feat(viewer): restyle toolbar and add line wrap toggle
This commit is contained in:
@@ -373,6 +373,40 @@ bool compactButton(const char* label, bool active = false) {
|
||||
return clicked;
|
||||
}
|
||||
|
||||
bool toolbarSegmentButton(const char* label, bool active, float width) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
||||
if (active) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.13f, 0.25f, 0.43f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.15f, 0.29f, 0.49f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.12f, 0.22f, 0.39f, 1.0f));
|
||||
}
|
||||
const bool clicked = ImGui::Button(label, {width, 0.0f});
|
||||
if (active) ImGui::PopStyleColor(3);
|
||||
ImGui::PopStyleVar();
|
||||
return clicked;
|
||||
}
|
||||
|
||||
int wrappedLineCount(std::string_view text, int wrap_columns) {
|
||||
if (wrap_columns <= 0 || static_cast<int>(text.size()) <= wrap_columns) return 1;
|
||||
int lines = 0;
|
||||
size_t cursor = 0;
|
||||
while (cursor < text.size()) {
|
||||
size_t remaining = text.size() - cursor;
|
||||
if (remaining <= static_cast<size_t>(wrap_columns)) {
|
||||
++lines;
|
||||
break;
|
||||
}
|
||||
size_t split = cursor + static_cast<size_t>(wrap_columns);
|
||||
size_t break_at = text.rfind(' ', split);
|
||||
if (break_at == std::string_view::npos || break_at < cursor + static_cast<size_t>(wrap_columns / 3))
|
||||
break_at = split;
|
||||
cursor = break_at;
|
||||
while (cursor < text.size() && text[cursor] == ' ') ++cursor;
|
||||
++lines;
|
||||
}
|
||||
return std::max(lines, 1);
|
||||
}
|
||||
|
||||
struct MinimapSegment {
|
||||
float weight = 0.0f;
|
||||
ImU32 color = syntax_normal;
|
||||
@@ -545,17 +579,43 @@ void drawMinimap(const std::vector<MinimapEntry>& entries, const ImVec2& size, f
|
||||
|
||||
void drawCodeLine(const std::string& text, SyntaxLanguage language, SyntaxState& syntax,
|
||||
float scale, ImU32 background = IM_COL32(0, 0, 0, 0), float left_gutter = 0.0f,
|
||||
float minimum_width = 0.0f, float custom_row_height = 0.0f) {
|
||||
const float row_height = custom_row_height > 0.0f ? custom_row_height : scaled(21.0f, scale);
|
||||
float minimum_width = 0.0f, float custom_row_height = 0.0f, int wrap_columns = 0) {
|
||||
const float base_row_height = scaled(21.0f, scale);
|
||||
const int visual_lines = wrappedLineCount(text, wrap_columns);
|
||||
const float row_height = custom_row_height > 0.0f ? custom_row_height : base_row_height * static_cast<float>(visual_lines);
|
||||
const ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
const float width = std::max(minimum_width, ImGui::CalcTextSize(text.c_str()).x + left_gutter + scaled(12.0f, scale));
|
||||
const float width = minimum_width > 0.0f ? minimum_width :
|
||||
std::max(minimum_width, ImGui::CalcTextSize(text.c_str()).x + left_gutter + scaled(12.0f, scale));
|
||||
ImGui::InvisibleButton("##code_line", {width, row_height});
|
||||
const ImVec2 minimum = ImGui::GetItemRectMin();
|
||||
const ImVec2 maximum = ImGui::GetItemRectMax();
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
if ((background & IM_COL32_A_MASK) != 0)
|
||||
draw->AddRectFilled(minimum, maximum, background);
|
||||
drawSyntaxText(draw, {start.x + left_gutter, start.y + scaled(2.0f, scale)}, text, language, syntax);
|
||||
if (wrap_columns <= 0 || static_cast<int>(text.size()) <= wrap_columns) {
|
||||
drawSyntaxText(draw, {start.x + left_gutter, start.y + scaled(2.0f, scale)}, text, language, syntax);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t cursor = 0;
|
||||
int line_index = 0;
|
||||
while (cursor < text.size()) {
|
||||
size_t remaining = text.size() - cursor;
|
||||
size_t chunk_end = text.size();
|
||||
if (remaining > static_cast<size_t>(wrap_columns)) {
|
||||
chunk_end = cursor + static_cast<size_t>(wrap_columns);
|
||||
size_t break_at = text.rfind(' ', chunk_end);
|
||||
if (break_at == std::string::npos || break_at < cursor + static_cast<size_t>(wrap_columns / 3))
|
||||
break_at = chunk_end;
|
||||
chunk_end = break_at;
|
||||
}
|
||||
const std::string chunk = text.substr(cursor, chunk_end - cursor);
|
||||
drawSyntaxText(draw, {start.x + left_gutter, start.y + scaled(2.0f, scale) + base_row_height * static_cast<float>(line_index)},
|
||||
chunk, language, syntax);
|
||||
cursor = chunk_end;
|
||||
while (cursor < text.size() && text[cursor] == ' ') ++cursor;
|
||||
++line_index;
|
||||
}
|
||||
}
|
||||
|
||||
void drawCodeLineNumber(int value, float x, float y, ImU32 color) {
|
||||
@@ -792,52 +852,43 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
ImGui::TextColored(ImVec4(0.94f, 0.66f, 0.25f, 1), ICON_TB_PEN);
|
||||
ImGui::SameLine(0, scaled(7, scale));
|
||||
ImGui::TextUnformatted(path_.c_str());
|
||||
|
||||
ImGui::SameLine(std::max(scaled(240, scale), ImGui::GetWindowWidth() - scaled(455, scale)));
|
||||
const bool historical = !commit_id_.empty();
|
||||
const std::string source_label = historical ? "Commit " + commit_id_.substr(0, 8)
|
||||
: staged_ ? "Staged" : "Unstaged";
|
||||
if (compactButton(source_label.c_str(), true)) {}
|
||||
ImGui::SameLine();
|
||||
if (compactButton("File View", mode_ == Mode::file)) {
|
||||
const float top_right_width = scaled(72.0f, scale);
|
||||
ImGui::SameLine(std::max(scaled(160.0f, scale), ImGui::GetWindowWidth() - top_right_width));
|
||||
ImGui::TextDisabled("UTF-8");
|
||||
ImGui::SameLine(0, scaled(10.0f, scale));
|
||||
if (compactButton(ICON_TB_XMARK)) close();
|
||||
ImGui::Separator();
|
||||
|
||||
const float segment_width = scaled(86.0f, scale);
|
||||
const float file_group_width = segment_width * 2.0f;
|
||||
const float blame_group_width = segment_width * 2.0f;
|
||||
const float wrap_width = scaled(34.0f, scale);
|
||||
const float between_groups = scaled(22.0f, scale);
|
||||
const float wrap_gap = scaled(12.0f, scale);
|
||||
const float toolbar_width = file_group_width + between_groups + blame_group_width + wrap_gap + wrap_width;
|
||||
ImGui::SetCursorPosX(std::max(0.0f, (ImGui::GetWindowWidth() - toolbar_width) * 0.5f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, ImGui::GetStyle().ItemSpacing.y});
|
||||
if (toolbarSegmentButton("File View", mode_ == Mode::file, segment_width)) {
|
||||
mode_ = Mode::file; loadSupplement(repository, manager, mode_, notice);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (compactButton("Diff View", mode_ == Mode::diff)) mode_ = Mode::diff;
|
||||
ImGui::SameLine(0, scaled(28, scale));
|
||||
if (compactButton("Blame", mode_ == Mode::blame)) {
|
||||
if (toolbarSegmentButton("Diff View", mode_ == Mode::diff, segment_width)) mode_ = Mode::diff;
|
||||
ImGui::SameLine(0, between_groups);
|
||||
if (toolbarSegmentButton("Blame", mode_ == Mode::blame, segment_width)) {
|
||||
mode_ = Mode::blame; loadSupplement(repository, manager, mode_, notice);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (compactButton("History", mode_ == Mode::history)) {
|
||||
if (toolbarSegmentButton("History", mode_ == Mode::history, segment_width)) {
|
||||
mode_ = Mode::history; loadSupplement(repository, manager, mode_, notice);
|
||||
}
|
||||
if (!historical) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.43f, 0.90f, 0.51f, 1));
|
||||
if (compactButton(staged_ ? "Unstage File" : "Stage File")) {
|
||||
const bool changed = staged_ ? manager.unstageFile(repository, path_, notice)
|
||||
: manager.stageFile(repository, path_, notice);
|
||||
if (changed) { staged_ = !staged_; reload(repository, manager, notice); }
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (compactButton(ICON_TB_XMARK)) close();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::SameLine(0, wrap_gap);
|
||||
toolbarSegmentButton(ICON_TB_BARS, line_wrap_, wrap_width);
|
||||
if (ImGui::IsItemClicked()) line_wrap_ = !line_wrap_;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort))
|
||||
ImGui::SetTooltip("%s", line_wrap_ ? "Disable line wrap" : "Enable line wrap");
|
||||
ImGui::Separator();
|
||||
if (!path_.empty()) {
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - scaled(116, scale));
|
||||
ImGui::TextDisabled("UTF-8");
|
||||
if (!historical && !staged_) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.94f, 0.44f, 0.44f, 1));
|
||||
if (compactButton(ICON_TB_TRASH_CAN)) {
|
||||
if (manager.discardFile(repository, path_, notice)) close();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
const bool show_minimap = mode_ == Mode::diff || mode_ == Mode::file || mode_ == Mode::blame || mode_ == Mode::history;
|
||||
const float minimap_width = scaled(168.0f, scale);
|
||||
@@ -877,7 +928,8 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::BeginChild("diff_content_main", ImVec2{-1, -1}, ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGuiWindowFlags content_flags = line_wrap_ ? ImGuiWindowFlags_None : ImGuiWindowFlags_HorizontalScrollbar;
|
||||
ImGui::BeginChild("diff_content_main", ImVec2{-1, -1}, ImGuiChildFlags_None, content_flags);
|
||||
const bool use_code_font = code_font && mode_ != Mode::history;
|
||||
if (use_code_font) ImGui::PushFont(code_font, 0.0f);
|
||||
const float row_height = scaled(21, scale);
|
||||
@@ -886,6 +938,11 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
const float content_width = std::max(
|
||||
ImGui::GetContentRegionAvail().x - (show_minimap ? minimap_width + minimap_gap + scrollbar_width + scaled(6.0f, scale) : 0.0f),
|
||||
scaled(200.0f, scale));
|
||||
const float wrapable_text_width = std::max(scaled(32.0f, scale), content_width - scaled(12.0f, scale));
|
||||
const float code_character_width = ImGui::CalcTextSize("M").x;
|
||||
const int wrap_columns = line_wrap_ && code_character_width > 0.0f
|
||||
? std::max(12, static_cast<int>(wrapable_text_width / code_character_width))
|
||||
: 0;
|
||||
|
||||
// Keep the first source row clear of the toolbar and aligned with a normal row inset.
|
||||
ImGui::Dummy({0.0f, row_height});
|
||||
@@ -938,7 +995,7 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
line.kind == LineKind::removed ? IM_COL32(86, 38, 42, 170) : IM_COL32(0, 0, 0, 0);
|
||||
drawCodeLine(line.text, language,
|
||||
line.kind == LineKind::removed ? old_syntax : new_syntax,
|
||||
scale, background, code_gutter, content_width);
|
||||
scale, background, code_gutter, content_width, 0.0f, wrap_columns);
|
||||
const float text_y = line_minimum.y + scaled(2.0f, scale);
|
||||
drawCodeLineNumber(line.old_number, line_minimum.x + scaled(6.0f, scale), text_y,
|
||||
IM_COL32(126, 132, 142, 255));
|
||||
@@ -970,7 +1027,7 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
const ImU32 background = line.show_attribution ? IM_COL32(39, 42, 50, 150) : IM_COL32(31, 34, 40, 105);
|
||||
const float blame_row_height = line.show_attribution ? scaled(28.0f, scale) : scaled(21.0f, scale);
|
||||
drawCodeLine(line.text, language, syntax, scale, background, code_gutter, content_width,
|
||||
blame_row_height);
|
||||
blame_row_height, wrap_columns);
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
const ImVec2 line_maximum = ImGui::GetItemRectMax();
|
||||
draw->AddRectFilled({line_minimum.x, line_minimum.y},
|
||||
@@ -1013,7 +1070,7 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac
|
||||
ImGui::PushID(static_cast<int>(index));
|
||||
const ImVec2 line_minimum = ImGui::GetCursorScreenPos();
|
||||
drawCodeLine((*lines)[index], language, syntax, scale, IM_COL32(0, 0, 0, 0),
|
||||
code_gutter, content_width);
|
||||
code_gutter, content_width, 0.0f, wrap_columns);
|
||||
drawCodeLineNumber(static_cast<int>(index + 1), line_minimum.x + scaled(6.0f, scale),
|
||||
line_minimum.y + scaled(2.0f, scale), IM_COL32(126, 132, 142, 255));
|
||||
ImGui::PopID();
|
||||
|
||||
@@ -60,6 +60,7 @@ private:
|
||||
std::vector<std::string> file_lines_;
|
||||
std::vector<BlameLine> blame_lines_;
|
||||
std::vector<HistoryEntry> history_entries_;
|
||||
bool line_wrap_ = false;
|
||||
|
||||
void reload(RepositoryView& repository, GitManager& manager, std::string& notice);
|
||||
void loadSupplement(RepositoryView& repository, GitManager& manager, Mode mode, std::string& notice);
|
||||
|
||||
Reference in New Issue
Block a user