perf(overview): clip the address list to the visible rows
The Overview address list built all N rows on every frame (icons, labels, balances, buttons, an InvisibleButton and a context-menu check per address) and allocated a vector of N row positions, however few rows fit on screen; the edge mask then walked all their vertices. An ImGuiListClipper now submits only the rows that intersect the list's clip rect, plus: - one guard row on each side, so dividers and anti-aliased edges at the clip boundary are drawn as before; - the row being dragged and the row whose context menu is open, so drag-to-reorder and the menu keep working when they scroll away; - the rows keyboard navigation targets (kept by the clipper itself). Before each row the cursor is put back exactly where the unclipped loop left it, so the density-scaled spacing, which depends on the cursor position, and every row's geometry are unchanged. Row tops are computed from the index, so the per-frame vector is gone, and the scroll extent is the same. The row model (sort, filter) is still rebuilt over all addresses each frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// Copy feedback
|
||||
static int s_copiedRow = -1;
|
||||
static float s_copiedTime = 0.0f;
|
||||
static int s_ctxRow = -1; // row whose context popup is open: always submitted (clipper must not drop it)
|
||||
|
||||
// ---- Build and filter address rows ----
|
||||
std::vector<AddressListInput> rowInputs;
|
||||
@@ -296,11 +297,11 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
ImU32 rowSelectCol = S.resolveColor("var(--row-select)", IM_COL32(255, 255, 255, 20));
|
||||
ImU32 dividerCol = S.resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15));
|
||||
|
||||
// Compute Y positions for all rows (pass 1)
|
||||
std::vector<float> rowY(rows.size());
|
||||
float cursorStartY = ImGui::GetCursorScreenPos().y;
|
||||
for (int i = 0; i < (int)rows.size(); ++i)
|
||||
rowY[i] = cursorStartY + i * rowH;
|
||||
// Row i's top is a pure function of its index (fixed pitch rowH): no per-frame table.
|
||||
const float cursorStartY = ImGui::GetCursorScreenPos().y;
|
||||
const float rowLineX = ImGui::GetCursorScreenPos().x; // every row's x (line start)
|
||||
const float rowItemSpacingY = ImGui::GetStyle().ItemSpacing.y;
|
||||
auto rowTop = [cursorStartY, rowH](int i) { return cursorStartY + i * rowH; };
|
||||
|
||||
// Drag logic — detect drag start, compute drag position
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
@@ -344,7 +345,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// Reorder — compute insert position from mouseY.
|
||||
int insertIdx = 0;
|
||||
for (int i = 0; i < (int)rows.size(); ++i) {
|
||||
if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
|
||||
if (mousePos.y > rowTop(i) + rowH * 0.5f) insertIdx = i + 1;
|
||||
}
|
||||
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1 &&
|
||||
s_dragIdx < (int)rows.size()) {
|
||||
@@ -369,14 +370,35 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
s_dropMode = 0;
|
||||
}
|
||||
|
||||
// ---- PASS 2: Render rows ----
|
||||
for (int row_idx = 0; row_idx < (int)rows.size(); row_idx++) {
|
||||
// ---- PASS 2: Render rows (visible rows only) ----
|
||||
// ImGuiListClipper submits only rows that intersect the child's clip rect (and, itself, the rows of an
|
||||
// ImGui nav request / the nav-focused row, so keyboard nav keeps working). Always submitted on top:
|
||||
// - one guard row on each side of the visible window (a row draws its divider on the next row's top);
|
||||
// - the row being dragged (its elevation + tooltip follow the mouse; its drag threshold is tested in
|
||||
// its own body) and the row whose context popup is open (the popup exists only while submitted).
|
||||
const int rowCount = (int)rows.size();
|
||||
ImGuiListClipper rowClipper;
|
||||
rowClipper.Begin(rowCount, rowH);
|
||||
{
|
||||
const AddressRowWindow guard = ComputeAddressRowWindow(dl->GetClipRectMin().y, dl->GetClipRectMax().y,
|
||||
cursorStartY, rowH, rowCount);
|
||||
rowClipper.IncludeItemsByIndex(guard.begin, guard.end);
|
||||
}
|
||||
if (s_dragIdx >= 0 && s_dragIdx < rowCount) rowClipper.IncludeItemByIndex(s_dragIdx);
|
||||
if (s_ctxRow >= rowCount) s_ctxRow = -1;
|
||||
if (s_ctxRow >= 0) rowClipper.IncludeItemByIndex(s_ctxRow);
|
||||
while (rowClipper.Step()) {
|
||||
for (int row_idx = rowClipper.DisplayStart; row_idx < rowClipper.DisplayEnd; row_idx++) {
|
||||
// Replay the cursor the unclipped loop had here: the Layout::spacing*() tokens in this body read
|
||||
// GetContentRegionAvail().y (densityScale), so every row keeps exactly its old geometry.
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowLineX,
|
||||
AddressRowEntryCursorY(row_idx, cursorStartY, rowH, rowItemSpacingY)));
|
||||
const auto& row = rows[row_idx];
|
||||
const auto& addr = *row.info;
|
||||
bool isDragged = (s_dragIdx == row_idx && s_dragActive);
|
||||
|
||||
// For dragged row, offset Y to follow mouse
|
||||
float drawY = rowY[row_idx];
|
||||
float drawY = rowTop(row_idx);
|
||||
if (isDragged) {
|
||||
drawY = mousePos.y - s_dragOffsetY;
|
||||
}
|
||||
@@ -648,7 +670,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
|
||||
// Advance cursor for interaction area
|
||||
ImGui::PushID(row_idx);
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowPos.x, rowY[row_idx]));
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowPos.x, rowTop(row_idx)));
|
||||
ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));
|
||||
if (ImGui::IsItemHovered() && !s_dragActive) {
|
||||
if (row.isZ) material::Tooltip("%s", addr.address.c_str());
|
||||
@@ -658,6 +680,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// Context menu
|
||||
const auto& acrTheme = GetCurrentAcrylicTheme();
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem("AddressContext", 0, acrTheme.menu)) {
|
||||
s_ctxRow = row_idx;
|
||||
if (ImGui::MenuItem(TR("copy_address"))) {
|
||||
ImGui::SetClipboardText(addr.address.c_str());
|
||||
s_copiedRow = row_idx;
|
||||
@@ -729,18 +752,21 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
} else if (s_ctxRow == row_idx) {
|
||||
s_ctxRow = -1; // its popup closed
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// Separator between rows
|
||||
if (row_idx < (int)rows.size() - 1 && selected_row != row_idx) {
|
||||
float sepY = rowY[row_idx] + rowH;
|
||||
float sepY = rowTop(row_idx) + rowH;
|
||||
dl->AddLine(
|
||||
ImVec2(rowPos.x + rowPadLeft + rowIconSz * 2.0f, sepY),
|
||||
ImVec2(rowPos.x + innerW - Layout::spacingLg(), sepY),
|
||||
dividerCol);
|
||||
}
|
||||
}
|
||||
} // while (rowClipper.Step())
|
||||
|
||||
// Advance cursor past all rows
|
||||
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x,
|
||||
|
||||
Reference in New Issue
Block a user