fix(graph): connect compact dynamic commit rows
This commit is contained in:
@@ -420,29 +420,40 @@ void draw_sidebar(float width) {
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
(void)lane;
|
||||
std::string ref_display_name(const RefBadge& badge) {
|
||||
std::string display_name = badge.name;
|
||||
if (badge.kind == RefKind::remote) {
|
||||
const size_t slash = display_name.find('/');
|
||||
if (slash != std::string::npos) display_name.erase(0, slash + 1);
|
||||
}
|
||||
return display_name;
|
||||
}
|
||||
|
||||
float outline_icon_width(const char* icon) {
|
||||
return g_outline_icon_font
|
||||
? g_outline_icon_font->CalcTextSizeA(g_outline_icon_size, 1000.0f, 0.0f, icon).x
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
float ref_badge_width(const RefBadge& badge) {
|
||||
const std::string display_name = ref_display_name(badge);
|
||||
float width = ImGui::CalcTextSize(display_name.c_str()).x + ui(12.0f);
|
||||
if (badge.current) width += outline_icon_width(ICON_TB_CHECK) + ui(5.0f);
|
||||
if (badge.worktree) width += ui(5.0f) + outline_icon_width(ICON_TB_DEVICE_LAPTOP);
|
||||
if (badge.kind == RefKind::remote || badge.upstream)
|
||||
width += ui(5.0f) + outline_icon_width(ICON_TB_CLOUD);
|
||||
if (badge.kind == RefKind::tag) width += ui(5.0f) + outline_icon_width(ICON_TB_TAG);
|
||||
return width;
|
||||
}
|
||||
|
||||
void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
(void)lane;
|
||||
const std::string display_name = ref_display_name(badge);
|
||||
|
||||
const bool show_cloud = badge.kind == RefKind::remote || badge.upstream;
|
||||
const bool show_tag = badge.kind == RefKind::tag;
|
||||
const ImVec2 label_size = ImGui::CalcTextSize(display_name.c_str());
|
||||
const auto icon_width = [](const char* icon) {
|
||||
return g_outline_icon_font
|
||||
? g_outline_icon_font->CalcTextSizeA(g_outline_icon_size, 1000.0f, 0.0f, icon).x
|
||||
: 0.0f;
|
||||
};
|
||||
float content_width = label_size.x;
|
||||
if (badge.current) content_width += icon_width(ICON_TB_CHECK) + ui(5.0f);
|
||||
if (badge.worktree) content_width += ui(5.0f) + icon_width(ICON_TB_DEVICE_LAPTOP);
|
||||
if (show_cloud) content_width += ui(5.0f) + icon_width(ICON_TB_CLOUD);
|
||||
if (show_tag) content_width += ui(5.0f) + icon_width(ICON_TB_TAG);
|
||||
|
||||
const ImVec2 chip_size{content_width + ui(12.0f), ui(23.0f)};
|
||||
const ImVec2 chip_size{ref_badge_width(badge), ui(23.0f)};
|
||||
ImGui::PushID(index);
|
||||
ImGui::InvisibleButton("##ref_badge", chip_size);
|
||||
const ImVec2 minimum = ImGui::GetItemRectMin();
|
||||
@@ -460,7 +471,7 @@ void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
const auto draw_icon = [&](const char* icon) {
|
||||
if (!g_outline_icon_font) return;
|
||||
draw->AddText(g_outline_icon_font, g_outline_icon_size, {x, icon_y}, color, icon);
|
||||
x += icon_width(icon) + ui(5.0f);
|
||||
x += outline_icon_width(icon) + ui(5.0f);
|
||||
};
|
||||
if (badge.current) draw_icon(ICON_TB_CHECK);
|
||||
draw->AddText({x, text_y}, color, display_name.c_str());
|
||||
@@ -480,19 +491,62 @@ void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
}
|
||||
|
||||
void draw_commit_table() {
|
||||
const auto commit_visible = [](const CommitInfo& commit) {
|
||||
if (!g_filter[0]) return true;
|
||||
if (commit.summary.find(g_filter.data()) != std::string::npos) return true;
|
||||
return std::any_of(commit.refs.begin(), commit.refs.end(), [](const RefBadge& ref) {
|
||||
return ref.name.find(g_filter.data()) != std::string::npos;
|
||||
});
|
||||
};
|
||||
|
||||
float widest_reference_row = 0.0f;
|
||||
for (const CommitInfo& commit : repo().commits) {
|
||||
float width = 0.0f;
|
||||
for (const RefBadge& badge : commit.refs)
|
||||
width += ref_badge_width(badge) + (width > 0.0f ? ui(4.0f) : 0.0f);
|
||||
widest_reference_row = std::max(widest_reference_row, width);
|
||||
}
|
||||
const float maximum_reference_width = std::max(ui(145.0f),
|
||||
std::min(ui(320.0f), ImGui::GetContentRegionAvail().x * 0.36f));
|
||||
const float reference_width = std::clamp(
|
||||
widest_reference_row + ui(12.0f), ui(145.0f), maximum_reference_width);
|
||||
const float chip_line_width = std::max(ui(40.0f), reference_width - ui(12.0f));
|
||||
std::vector<float> row_heights(repo().commits.size(), 0.0f);
|
||||
for (size_t index = 0; index < repo().commits.size(); ++index) {
|
||||
const CommitInfo& commit = repo().commits[index];
|
||||
if (!commit_visible(commit)) continue;
|
||||
int lines = commit.refs.empty() ? 0 : 1;
|
||||
float line_width = 0.0f;
|
||||
for (const RefBadge& badge : commit.refs) {
|
||||
const float badge_width = ref_badge_width(badge);
|
||||
const float spacing = line_width > 0.0f ? ui(4.0f) : 0.0f;
|
||||
if (line_width > 0.0f && line_width + spacing + badge_width > chip_line_width) {
|
||||
++lines;
|
||||
line_width = badge_width;
|
||||
} else {
|
||||
line_width += spacing + badge_width;
|
||||
}
|
||||
}
|
||||
row_heights[index] = std::max(ui(26.0f), lines * ui(25.0f) + ui(1.0f));
|
||||
}
|
||||
|
||||
ImGuiTableFlags flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
|
||||
ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp;
|
||||
if (!ImGui::BeginTable("commits", 4, flags, {-1, -1})) return;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, {ImGui::GetStyle().CellPadding.x, ui(1.0f)});
|
||||
if (!ImGui::BeginTable("commits", 4, flags, {-1, -1})) {
|
||||
ImGui::PopStyleVar();
|
||||
return;
|
||||
}
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
const GraphRenderer graph(g_ui_scale);
|
||||
const float graph_width = GraphRenderer::requiredWidth(repo().commits, g_ui_scale);
|
||||
ImGui::TableSetupColumn("BRANCH / TAG", ImGuiTableColumnFlags_WidthFixed, ui(145.0f));
|
||||
ImGui::TableSetupColumn("BRANCH / TAG", ImGuiTableColumnFlags_WidthFixed, reference_width);
|
||||
ImGui::TableSetupColumn("GRAPH", ImGuiTableColumnFlags_WidthFixed, graph_width);
|
||||
ImGui::TableSetupColumn("COMMIT MESSAGE", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("COMMIT DATE / TIME", ImGuiTableColumnFlags_WidthFixed, ui(180.0f));
|
||||
ImGui::TableHeadersRow();
|
||||
if (!repo().working_files.empty()) {
|
||||
ImGui::TableNextRow(0, ui(31.0f));
|
||||
ImGui::TableNextRow(0, ui(26.0f));
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
if (ImGui::Selectable("##working_tree", repo().selected_commit == -1,
|
||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap))
|
||||
@@ -500,9 +554,9 @@ void draw_commit_table() {
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
const ImVec2 position = ImGui::GetCursorScreenPos();
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
const ImVec2 center{position.x + ui(15), position.y + ui(15.5f)};
|
||||
const ImVec2 center{position.x + ui(15), position.y + ui(13.0f)};
|
||||
draw->AddCircle(center, ui(7), IM_COL32(90, 180, 195, 210), 12, ui(1.5f));
|
||||
ImGui::Dummy({0, ui(31)});
|
||||
ImGui::Dummy({0, ui(26)});
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::TextDisabled("// WIP");
|
||||
ImGui::SameLine();
|
||||
@@ -511,15 +565,11 @@ void draw_commit_table() {
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(repo().commits.size()); ++i) {
|
||||
const auto& commit = repo().commits[i];
|
||||
if (g_filter[0]) {
|
||||
const bool message_matches = commit.summary.find(g_filter.data()) != std::string::npos;
|
||||
const bool ref_matches = std::any_of(commit.refs.begin(), commit.refs.end(), [](const RefBadge& ref) {
|
||||
return ref.name.find(g_filter.data()) != std::string::npos;
|
||||
});
|
||||
if (!message_matches && !ref_matches) continue;
|
||||
}
|
||||
ImGui::TableNextRow(0, ui(31.0f));
|
||||
if (!commit_visible(commit)) continue;
|
||||
const float row_height = row_heights[static_cast<size_t>(i)];
|
||||
ImGui::TableNextRow(0, row_height);
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
const ImVec2 reference_origin = ImGui::GetCursorScreenPos();
|
||||
std::string row_id = "##commit" + std::to_string(i);
|
||||
if (ImGui::Selectable(row_id.c_str(), repo().selected_commit == i,
|
||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) repo().selected_commit = i;
|
||||
@@ -536,19 +586,28 @@ void draw_commit_table() {
|
||||
if (ImGui::MenuItem(ICON_FA_CODE_BRANCH " Create branch here")) g_notice = "Branch creation is not wired yet";
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine(0, ui(3));
|
||||
float chip_x = reference_origin.x + ui(3.0f);
|
||||
float chip_y = reference_origin.y + ui(1.5f);
|
||||
const float chip_right = reference_origin.x + chip_line_width;
|
||||
for (int ref_index = 0; ref_index < static_cast<int>(commit.refs.size()); ++ref_index) {
|
||||
if (ref_index > 0) ImGui::SameLine(0, ui(4));
|
||||
const float badge_width = ref_badge_width(commit.refs[ref_index]);
|
||||
if (chip_x > reference_origin.x + ui(3.0f) && chip_x + badge_width > chip_right) {
|
||||
chip_x = reference_origin.x + ui(3.0f);
|
||||
chip_y += ui(25.0f);
|
||||
}
|
||||
ImGui::SetCursorScreenPos({chip_x, chip_y});
|
||||
draw_ref_badge(commit.refs[ref_index], i * 1000 + ref_index, commit.lane);
|
||||
chip_x += badge_width + ui(4.0f);
|
||||
}
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
graph.drawRow(i, commit, repo().commits, g_avatar_cache);
|
||||
graph.drawRow(i, commit, repo().commits, row_heights, g_avatar_cache);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::TextUnformatted(commit.summary.c_str());
|
||||
ImGui::TableSetColumnIndex(3);
|
||||
ImGui::TextDisabled("%s", commit.date.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
const char* change_icon(FileChangeKind kind) {
|
||||
|
||||
@@ -25,54 +25,76 @@ ImU32 GraphRenderer::laneColor(int lane, int alpha) {
|
||||
float GraphRenderer::requiredWidth(const std::vector<CommitInfo>& commits, float scale) {
|
||||
int maximum_lane = 0;
|
||||
for (const auto& commit : commits) maximum_lane = std::max(maximum_lane, commit.lane);
|
||||
return std::clamp((42.0f + maximum_lane * 20.0f) * scale, 62.0f * scale, 180.0f * scale);
|
||||
return std::clamp((42.0f + maximum_lane * 18.0f) * scale, 56.0f * scale, 220.0f * scale);
|
||||
}
|
||||
|
||||
void GraphRenderer::drawRow(int row, const CommitInfo& commit,
|
||||
const std::vector<CommitInfo>& commits, AvatarCache* avatars) const {
|
||||
const std::vector<CommitInfo>& commits, const std::vector<float>& row_heights,
|
||||
AvatarCache* avatars) const {
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||
constexpr float logical_row_height = 31.0f;
|
||||
const float row_height = px(logical_row_height);
|
||||
const float lane_spacing = px(20.0f);
|
||||
const float row_height = row_heights[static_cast<size_t>(row)];
|
||||
const float content_height = std::max(px(1.0f), row_height - ImGui::GetStyle().CellPadding.y * 2.0f);
|
||||
const float lane_spacing = px(18.0f);
|
||||
const float x = origin.x + px(15.0f) + lane_spacing * commit.lane;
|
||||
const float y = origin.y + row_height * 0.5f;
|
||||
const float y = origin.y + content_height * 0.5f;
|
||||
|
||||
// Draw parent paths first so nodes remain crisp above every connection.
|
||||
for (const git_oid& parent_id : commit.parent_ids) {
|
||||
std::vector<float> row_offsets(row_heights.size() + 1, 0.0f);
|
||||
for (size_t index = 0; index < row_heights.size(); ++index)
|
||||
row_offsets[index + 1] = row_offsets[index] + row_heights[index];
|
||||
const float graph_top = origin.y - row_offsets[static_cast<size_t>(row)];
|
||||
const auto center_y = [&](int index) {
|
||||
return graph_top + row_offsets[static_cast<size_t>(index)] +
|
||||
(row_heights[static_cast<size_t>(index)] - ImGui::GetStyle().CellPadding.y * 2.0f) * 0.5f;
|
||||
};
|
||||
|
||||
// Every row redraws edges crossing its clip rectangle. This keeps long paths continuous
|
||||
// without allowing table row clipping to cut out intermediate segments.
|
||||
for (int child_row = 0; child_row < static_cast<int>(commits.size()); ++child_row) {
|
||||
if (row_heights[static_cast<size_t>(child_row)] <= 0.0f) continue;
|
||||
const CommitInfo& child = commits[static_cast<size_t>(child_row)];
|
||||
for (const git_oid& parent_id : child.parent_ids) {
|
||||
const auto parent = std::find_if(commits.begin(), commits.end(), [&parent_id](const CommitInfo& item) {
|
||||
return git_oid_equal(&item.oid, &parent_id) != 0;
|
||||
});
|
||||
if (parent == commits.end()) continue;
|
||||
|
||||
const int parent_row = static_cast<int>(std::distance(commits.begin(), parent));
|
||||
const float parent_x = origin.x + px(15.0f) + lane_spacing * parent->lane;
|
||||
const float parent_y = y + row_height * (parent_row - row);
|
||||
const ImU32 color = laneColor(commit.lane, 220);
|
||||
if (parent_row <= child_row || row < child_row || row > parent_row ||
|
||||
row_heights[static_cast<size_t>(parent_row)] <= 0.0f) continue;
|
||||
|
||||
if (parent->lane == commit.lane) {
|
||||
draw->AddLine({x, y}, {x, parent_y}, color, px(2.0f));
|
||||
const float child_x = origin.x + px(15.0f) + lane_spacing * child.lane;
|
||||
const float parent_x = origin.x + px(15.0f) + lane_spacing * parent->lane;
|
||||
const float child_y = center_y(child_row);
|
||||
const float parent_y = center_y(parent_row);
|
||||
const int edge_lane = child.lane == parent->lane ? child.lane : parent->lane;
|
||||
const ImU32 color = laneColor(edge_lane, 225);
|
||||
if (child.lane == parent->lane) {
|
||||
draw->AddLine({child_x, child_y}, {parent_x, parent_y}, color, px(1.8f));
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distance = std::max(0.0f, parent_y - y);
|
||||
const float curve_end_y = std::min(parent_y - px(5.0f), y + std::min(px(22.0f), distance * 0.45f));
|
||||
const float control_y = y + std::min(px(11.0f), distance * 0.22f);
|
||||
draw->AddBezierCubic({x, y}, {x, control_y}, {parent_x, control_y},
|
||||
{parent_x, curve_end_y}, color, px(2.0f));
|
||||
const float bend_height = std::min(parent_y - child_y, px(24.0f));
|
||||
const float curve_end_y = child_y + bend_height;
|
||||
draw->AddBezierCubic(
|
||||
{child_x, child_y},
|
||||
{child_x, child_y + bend_height * 0.45f},
|
||||
{parent_x, child_y + bend_height * 0.55f},
|
||||
{parent_x, curve_end_y},
|
||||
color, px(1.8f));
|
||||
if (curve_end_y < parent_y)
|
||||
draw->AddLine({parent_x, curve_end_y}, {parent_x, parent_y}, color, px(2.0f));
|
||||
draw->AddLine({parent_x, curve_end_y}, {parent_x, parent_y}, color, px(1.8f));
|
||||
}
|
||||
}
|
||||
|
||||
const ImU32 lane_color = laneColor(commit.lane);
|
||||
if (commit.parents > 1) {
|
||||
draw->AddCircleFilled({x, y}, px(4.5f), lane_color);
|
||||
draw->AddCircle({x, y}, px(6.0f), laneColor(commit.lane, 110), 0, px(1.0f));
|
||||
ImGui::Dummy({px(170.0f), row_height});
|
||||
ImGui::Dummy({0.0f, content_height});
|
||||
return;
|
||||
}
|
||||
|
||||
const float radius = px(8.5f);
|
||||
const float radius = px(7.2f);
|
||||
draw->AddCircleFilled({x, y}, radius + px(2.0f), IM_COL32(19, 24, 31, 255));
|
||||
draw->AddCircle({x, y}, radius + px(1.0f), lane_color, 0, px(2.0f));
|
||||
draw->AddCircleFilled({x, y}, radius - px(1.0f), IM_COL32(232, 238, 242, 255));
|
||||
@@ -87,5 +109,5 @@ void GraphRenderer::drawRow(int row, const CommitInfo& commit,
|
||||
const ImVec2 icon_size = ImGui::CalcTextSize(ICON_FA_USER);
|
||||
draw->AddText({x - icon_size.x * 0.5f, y - icon_size.y * 0.5f}, lane_color, ICON_FA_USER);
|
||||
}
|
||||
ImGui::Dummy({px(170.0f), row_height});
|
||||
ImGui::Dummy({0.0f, content_height});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public:
|
||||
static float requiredWidth(const std::vector<CommitInfo>& commits, float scale);
|
||||
|
||||
void drawRow(int row, const CommitInfo& commit, const std::vector<CommitInfo>& commits,
|
||||
AvatarCache* avatars) const;
|
||||
const std::vector<float>& row_heights, AvatarCache* avatars) const;
|
||||
|
||||
private:
|
||||
float px(float value) const { return value * scale_; }
|
||||
|
||||
Reference in New Issue
Block a user