From 9ebf3699adb00fa8fe4fb967d0964fbf2125819e Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Fri, 19 Jun 2026 00:50:55 -0500 Subject: [PATCH] fix(diff): make code minimap scrollable and compact --- src/ui/diff_viewer.cpp | 100 ++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/src/ui/diff_viewer.cpp b/src/ui/diff_viewer.cpp index 930a772..0587043 100644 --- a/src/ui/diff_viewer.cpp +++ b/src/ui/diff_viewer.cpp @@ -518,27 +518,48 @@ std::vector buildMinimapSegments(const std::string& text, Syntax return segments; } -void drawMinimap(const std::vector& entries, const ImVec2& size, float scale, - float rendered_content_height, float visible_start, float visible_height, float max_scroll_y) { +ImU32 dimMinimapColor(ImU32 color) { + ImVec4 rgba = ImGui::ColorConvertU32ToFloat4(color); + rgba.x *= 0.58f; + rgba.y *= 0.58f; + rgba.z *= 0.58f; + rgba.w *= 0.82f; + return ImGui::ColorConvertFloat4ToU32(rgba); +} + +float minimapTotalUnits(const std::vector& entries) { + float total_units = 0.0f; + for (const MinimapEntry& entry : entries) total_units += std::max(0.10f, entry.units); + return std::max(1.0f, total_units); +} + +void drawMinimap(const std::vector& entries, const ImVec2& viewport_size, float scale, + float rendered_content_height, float visible_start, float visible_height, float max_scroll_y, + float content_height_override = 0.0f) { const ImVec2 minimum = ImGui::GetCursorScreenPos(); - ImGui::InvisibleButton("##code_minimap", size); + const float total_units = minimapTotalUnits(entries); + const float min_unit_height = scaled(0.72f, scale); + const float ideal_unit_height = scaled(2.05f, scale); + const float unit_height = content_height_override > 0.0f + ? std::max(min_unit_height, content_height_override / total_units) + : ideal_unit_height; + const float content_height = std::max(content_height_override, total_units * unit_height); + const ImVec2 content_size{viewport_size.x, content_height}; + ImGui::InvisibleButton("##code_minimap", content_size); const bool hovered = ImGui::IsItemHovered(); const bool active = ImGui::IsItemActive(); ImDrawList* draw = ImGui::GetWindowDrawList(); - draw->AddRectFilled(minimum, {minimum.x + size.x, minimum.y + size.y}, IM_COL32(31, 33, 39, 235), scaled(4.0f, scale)); - draw->AddRect(minimum, {minimum.x + size.x, minimum.y + size.y}, - hovered || active ? IM_COL32(82, 90, 103, 255) : IM_COL32(56, 61, 71, 255), scaled(4.0f, scale)); + draw->AddRectFilled(minimum, {minimum.x + viewport_size.x, minimum.y + content_size.y}, + IM_COL32(26, 28, 33, 188), scaled(3.0f, scale)); + draw->AddRect(minimum, {minimum.x + viewport_size.x, minimum.y + content_size.y}, + hovered || active ? IM_COL32(70, 76, 88, 180) : IM_COL32(46, 50, 58, 140), scaled(3.0f, scale)); - if (entries.empty() || size.y <= 2.0f) return; + if (entries.empty() || content_size.y <= 1.0f) return; const float content_left = minimum.x + scaled(5.0f, scale); - const float content_right = minimum.x + size.x - scaled(5.0f, scale); + const float content_right = minimum.x + viewport_size.x - scaled(5.0f, scale); const float content_width = std::max(1.0f, content_right - content_left); - float total_units = 0.0f; - for (const MinimapEntry& entry : entries) total_units += std::max(0.10f, entry.units); - total_units = std::max(1.0f, total_units); - const float unit_height = size.y / total_units; - const float line_height = std::max(1.0f, std::min(scaled(3.2f, scale), unit_height)); + const float line_height = std::max(scaled(0.65f, scale), std::min(scaled(2.1f, scale), unit_height)); float y = minimum.y; for (const MinimapEntry& entry : entries) { const float entry_units = std::max(0.10f, entry.units); @@ -552,29 +573,29 @@ void drawMinimap(const std::vector& entries, const ImVec2& size, f } const float width = std::max(scaled(1.0f, scale), content_width * (segment.weight / total_weight)); draw->AddRectFilled({x, y}, {std::min(content_right, x + width), y + line_height}, - segment.color, scaled(1.0f, scale)); + dimMinimapColor(segment.color), scaled(1.0f, scale)); x += width; } y += entry_units * unit_height; } - const float content_height = std::max(rendered_content_height, 1.0f); - const float clamped_visible_height = std::clamp(visible_height, 0.0f, content_height); - const float clamped_visible_start = std::clamp(visible_start, 0.0f, std::max(0.0f, content_height - clamped_visible_height)); - const float viewport_height = std::clamp(size.y * (clamped_visible_height / content_height), - scaled(18.0f, scale), size.y); - const float viewport_y = minimum.y + size.y * (clamped_visible_start / content_height); + const float rendered_height = std::max(rendered_content_height, 1.0f); + const float clamped_visible_height = std::clamp(visible_height, 0.0f, rendered_height); + const float clamped_visible_start = std::clamp(visible_start, 0.0f, std::max(0.0f, rendered_height - clamped_visible_height)); + const float viewport_height = std::clamp(content_size.y * (clamped_visible_height / rendered_height), + scaled(14.0f, scale), content_size.y); + const float viewport_y = minimum.y + content_size.y * (clamped_visible_start / rendered_height); if ((hovered || active) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { - const float mouse_ratio = std::clamp((ImGui::GetIO().MousePos.y - minimum.y) / std::max(1.0f, size.y), 0.0f, 1.0f); - const float target = std::clamp(mouse_ratio * content_height - clamped_visible_height * 0.5f, 0.0f, max_scroll_y); + const float mouse_ratio = std::clamp((ImGui::GetIO().MousePos.y - minimum.y) / std::max(1.0f, content_size.y), 0.0f, 1.0f); + const float target = std::clamp(mouse_ratio * rendered_height - clamped_visible_height * 0.5f, 0.0f, max_scroll_y); ImGui::SetScrollY(target); } draw->AddRectFilled({minimum.x + scaled(1.0f, scale), viewport_y}, - {minimum.x + size.x - scaled(1.0f, scale), viewport_y + viewport_height}, - IM_COL32(120, 146, 198, hovered || active ? 62 : 42), scaled(2.0f, scale)); + {minimum.x + viewport_size.x - scaled(1.0f, scale), viewport_y + viewport_height}, + IM_COL32(109, 129, 170, hovered || active ? 44 : 28), scaled(2.0f, scale)); draw->AddRect({minimum.x + scaled(1.0f, scale), viewport_y}, - {minimum.x + size.x - scaled(1.0f, scale), viewport_y + viewport_height}, - IM_COL32(120, 146, 198, 185), scaled(2.0f, scale)); + {minimum.x + viewport_size.x - scaled(1.0f, scale), viewport_y + viewport_height}, + IM_COL32(120, 138, 170, 120), scaled(2.0f, scale)); } void drawCodeLine(const std::string& text, SyntaxLanguage language, SyntaxState& syntax, @@ -965,7 +986,8 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac } ImGuiWindowFlags content_flags = line_wrap_ ? ImGuiWindowFlags_None : ImGuiWindowFlags_HorizontalScrollbar; - if (scroll_to_top_) ImGui::SetNextWindowScroll({0.0f, 0.0f}); + const bool request_scroll_to_top = scroll_to_top_; + if (request_scroll_to_top) ImGui::SetNextWindowScroll({0.0f, 0.0f}); ImGui::BeginChild("diff_content_main", ImVec2{-1, -1}, ImGuiChildFlags_None, content_flags); scroll_to_top_ = false; const bool use_code_font = code_font && mode_ != Mode::history; @@ -1160,14 +1182,28 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, AvatarCac rendered_content_height = ImGui::GetCursorPosY(); const float max_scroll_y = ImGui::GetScrollMaxY(); if (show_minimap) { - const float minimap_height = std::max(scaled(40.0f, scale), child_size.y - scaled(10.0f, scale)); + const float total_units = minimapTotalUnits(minimap_entries); + const float min_unit_height = scaled(0.72f, scale); + const float ideal_unit_height = scaled(2.05f, scale); + const float max_minimap_height = std::max(scaled(24.0f, scale), child_size.y - scaled(8.0f, scale)); + const float fitted_unit_height = std::clamp(max_minimap_height / total_units, min_unit_height, ideal_unit_height); + const float minimap_content_height = total_units * fitted_unit_height; + const float minimap_height = std::min(max_minimap_height, minimap_content_height); const float minimap_x = child_minimum.x + child_size.x - scrollbar_width - minimap_width - scaled(4.0f, scale); const float minimap_y = child_minimum.y + scaled(4.0f, scale); ImGui::SetCursorScreenPos({minimap_x, minimap_y}); - ImGui::PushClipRect(child_minimum, {child_minimum.x + child_size.x, child_minimum.y + child_size.y}, true); - drawMinimap(minimap_entries, {minimap_width, minimap_height}, scale, - rendered_content_height, main_scroll_y, main_window_height, max_scroll_y); - ImGui::PopClipRect(); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.28f, 0.31f, 0.37f, 0.55f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.36f, 0.40f, 0.47f, 0.72f)); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.44f, 0.49f, 0.57f, 0.86f)); + if (request_scroll_to_top) ImGui::SetNextWindowScroll({0.0f, 0.0f}); + ImGui::BeginChild("diff_content_minimap", {minimap_width, minimap_height}, + ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove); + drawMinimap(minimap_entries, {minimap_width - scaled(1.0f, scale), minimap_height}, scale, + rendered_content_height, main_scroll_y, main_window_height, max_scroll_y, minimap_content_height); + ImGui::EndChild(); + ImGui::PopStyleColor(5); } if (use_code_font) ImGui::PopFont(); ImGui::EndChild();