fix(market): stop the last portfolio row from clipping

Inside the portfolio row child each row emits two items (the InvisibleButton
hit target + a gap Dummy), and ImGui inserts its default ItemSpacing.y between
every item — so the real content ran ~4x ItemSpacing taller than the child's
height budget, clipping the bottom row (e.g. "Cold storage" lost its second
line) and widening the inter-row gaps.

Zero ItemSpacing inside the row child (the inter-row gap is set explicitly by
the Dummy) and size the child to the visible rows plus one gap of bottom
breathing room, so the last row never sits on the clip edge. Scale-independent
(rowH/gap both scale by dpiScale). Verified via the headless sweep across all
three row styles — every row now renders fully and the gaps tighten to rowGap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 20:37:43 -05:00
parent 8846a96e3d
commit 76a2ef51f3

View File

@@ -1708,6 +1708,10 @@ static void mktDrawPortfolio(const MktCtx& cx)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::BeginChild("##pfRows", ImVec2(availWidth, rowsH), false); ImGui::BeginChild("##pfRows", ImVec2(availWidth, rowsH), false);
ImGui::PopStyleVar(); ImGui::PopStyleVar();
// Zero item-spacing INSIDE the child: each row emits an InvisibleButton + a gap Dummy, and
// ImGui's default ItemSpacing.y between those items would inflate the content past rowsH and
// clip the last row (and widen the gaps). The inter-row gap is controlled by the Dummy below.
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImDrawList* rdl = ImGui::GetWindowDrawList(); ImDrawList* rdl = ImGui::GetWindowDrawList();
float rowW = ImGui::GetContentRegionAvail().x; // narrows automatically if a scrollbar shows float rowW = ImGui::GetContentRegionAvail().x; // narrows automatically if a scrollbar shows
if (vis.empty()) { if (vis.empty()) {
@@ -1728,6 +1732,7 @@ static void mktDrawPortfolio(const MktCtx& cx)
if (vi + 1 < (int)vis.size()) ImGui::Dummy(ImVec2(rowW, rowGap)); if (vi + 1 < (int)vis.size()) ImGui::Dummy(ImVec2(rowW, rowGap));
} }
} }
ImGui::PopStyleVar(); // ItemSpacing
ImGui::EndChild(); ImGui::EndChild();
} }
@@ -1836,9 +1841,13 @@ void RenderMarketTab(App* app)
float pfRowH = (pfStyle == 0 ? 46.0f : pfStyle == 1 ? 64.0f : 84.0f) * mktDp; float pfRowH = (pfStyle == 0 ? 46.0f : pfStyle == 1 ? 64.0f : 84.0f) * mktDp;
float pfRowGap = Layout::spacingSm(); float pfRowGap = Layout::spacingSm();
const int pfMaxVisibleRows = 4; // rows shown before the list scrolls const int pfMaxVisibleRows = 4; // rows shown before the list scrolls
float pfWantH = (pfVisN > 0) ? (pfVisN * pfRowH + std::max(0, pfVisN - 1) * pfRowGap) : 0.0f; // Height for up to pfMaxVisibleRows rows. ItemSpacing is zeroed inside the row child, so the
float pfCapH = pfMaxVisibleRows * pfRowH + (pfMaxVisibleRows - 1) * pfRowGap; // content is exactly rows + inter-row gaps; one extra gap of bottom breathing room keeps the
float pfGroupsH = std::min(pfWantH, pfCapH); // last visible row off the clip edge. More groups than this scroll internally.
int pfVisRows = std::min(pfVisN, pfMaxVisibleRows);
float pfGroupsH = (pfVisRows > 0)
? (pfVisRows * pfRowH + (pfVisRows - 1) * pfRowGap + pfRowGap)
: 0.0f;
float portfolioH = pfSummaryH + pfGroupsH; float portfolioH = pfSummaryH + pfGroupsH;
// Chart series for the selected interval (timestamped), shared by the hero change badge and // Chart series for the selected interval (timestamped), shared by the hero change badge and