fix(diff): make code minimap scrollable and compact

This commit is contained in:
2026-06-19 00:50:55 -05:00
parent 258973da57
commit 9ebf3699ad

View File

@@ -518,27 +518,48 @@ std::vector<MinimapSegment> buildMinimapSegments(const std::string& text, Syntax
return segments;
}
void drawMinimap(const std::vector<MinimapEntry>& 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<MinimapEntry>& 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<MinimapEntry>& 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<MinimapEntry>& 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();