diff --git a/HellionChat/Ui/Components/CardClipPlanner.cs b/HellionChat/Ui/Components/CardClipPlanner.cs new file mode 100644 index 0000000..11d744f --- /dev/null +++ b/HellionChat/Ui/Components/CardClipPlanner.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; + +namespace HellionChat.Ui.Components; + +// B2 (PERF-B2): variable-height clip plan for card mode. ImGuiListClipper needs +// a constant row height, so card mode (rows wrap to arbitrary heights) computes +// its own plan from the cached per-row heights: a lead dummy for the rows above +// the viewport, the [first..last] index range that overlaps the viewport, and +// an end dummy for the rows below. This is the prefix-sum analogue of +// OtterGui's GetNecessarySkips, but for non-uniform heights — kept Dalamud-free +// so the Build Suite pins every edge case. +// TEST-MIRROR: ../../../../Hellion Build test/Ui/CardClipPlanTests.cs +internal readonly record struct CardClipPlan( + int FirstVisible, + int LastVisible, + float LeadDummyHeight, + float EndDummyHeight +); + +internal static class CardClipPlanner +{ + // heights[i] is the cached, spacing-inclusive height of row i in draw order. + // A row overlaps the viewport iff rowTop < windowBottom && rowBottom > + // windowTop. FirstVisible/LastVisible are -1 when nothing overlaps (empty + // list); callers then submit a single end dummy of the full content height + // so the scrollbar stays correct. + internal static CardClipPlan Plan( + IReadOnlyList heights, + float scrollY, + float viewportHeight + ) + { + var count = heights.Count; + if (count == 0) + return new CardClipPlan(-1, -1, 0f, 0f); + + var windowTop = scrollY; + var windowBottom = scrollY + viewportHeight; + + var first = -1; + var last = -1; + var leadDummy = 0f; + var endDummy = 0f; + + var cursor = 0f; // running prefix sum = top edge of the current row + for (var i = 0; i < count; i++) + { + var rowTop = cursor; + var rowBottom = cursor + heights[i]; + var overlaps = rowTop < windowBottom && rowBottom > windowTop; + + if (overlaps) + { + if (first < 0) + first = i; + last = i; + } + else if (first < 0) + { + // Still above the visible window -> grows the lead dummy. + leadDummy += heights[i]; + } + else + { + // Already past the visible window -> grows the end dummy. + endDummy += heights[i]; + } + + cursor = rowBottom; + } + + return new CardClipPlan(first, last, leadDummy, endDummy); + } +}