From 4d95ef68f6a626dac4e274c57ee6eec6979f4d27 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 14 Oct 2026 16:55:07 -0500 Subject: [PATCH] perf(contacts): clip off-screen rows in the Cards and List views The contacts Cards/List row loop submitted every contact every frame. It now runs through an ImGuiListClipper, so only the on-screen rows are built; the fixed step (row height plus the item spacing pushed above) matches the per-row cursor exactly, so the scroll extent, fade, hover actions, selection and keyboard navigation are unchanged. Table view is left unclipped (its rows can wrap). Verified on DX11 (Windows) at normal and large window sizes and at font scale 1.5, in both Cards and List views. Co-Authored-By: Claude Opus 4.8 --- src/ui/windows/contacts_tab.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ui/windows/contacts_tab.cpp b/src/ui/windows/contacts_tab.cpp index 89b8926..90ba1d2 100644 --- a/src/ui/windows/contacts_tab.cpp +++ b/src/ui/windows/contacts_tab.cpp @@ -1310,8 +1310,15 @@ void RenderContactsTab(App* app) const float lblSz = lblF->LegacySize * sc; // scaled label + address text const float adrSz = adrF->LegacySize * sc; bool rowDeleteRequested = false; // per-row delete is deferred until after the loop - for (size_t vi = 0; vi < visibleRows.size(); ++vi) { - size_t i = visibleRows[vi]; + // C183(c): clip the Cards/List rows. Fixed step = the Selectable height (rowH) plus the + // ItemSpacing.y pushed above (8*dp card mode, 0 list mode); each row restores the cursor to + // `afterRow`, so the step is exact and the scroll extent is unchanged. (Table mode is NOT + // clipped -- its rows can wrap.) + ImGuiListClipper rowClipper; + rowClipper.Begin(static_cast(visibleRows.size()), rowH + (asCard ? 8.0f * dp : 0.0f)); + while (rowClipper.Step()) { + for (int vi = rowClipper.DisplayStart; vi < rowClipper.DisplayEnd; ++vi) { + size_t i = visibleRows[static_cast(vi)]; const auto& entry = book.entries()[i]; ImGui::PushID(static_cast(i)); const bool selected = (s_selected_index == static_cast(i)); @@ -1424,6 +1431,7 @@ void RenderContactsTab(App* app) ImGui::SetCursorScreenPos(afterRow); // undo the action buttons' cursor moves ImGui::PopID(); } + } // while (rowClipper.Step()) // Run the deferred per-row delete now that the loop over book.entries() has finished // (doDelete's two-stage confirm arms on the first click and removes on the second). if (rowDeleteRequested) doDelete();