fix(wallets): roomier cards, no spurious scrollbar

Two issues on the card list: it showed a scrollbar (and clipped the last card)
with only a few wallets, and the cards felt cramped.

- Scrollbar: the per-card advance added a trailing gap after EVERY card (incl.
  the last) and ImGui inserted its default ItemSpacing between the Dummy items,
  so the content ran ~1 gap taller than the child's height budget -> overflow ->
  scrollbar + clipped card. Zero ItemSpacing inside the list child, register each
  card's footprint with a Dummy, and gap only BETWEEN cards (no trailing gap), so
  the content matches the budget exactly. Sized the list to fit up to 7 cards
  before it scrolls.
- Roominess: bigger name (subtitle1), more vertical padding (spacingMd), and a
  touch more space after the icon.
- Removed the full-card InvisibleButton (hover is IsMouseHoveringRect again): its
  overlap with the action button tripped ImGui's ConfigDebugHighlightIdConflicts,
  drawing a red debug border around the list at 150% DPI.

Verified via the headless sweep at 100% and font_scale 1.5 — no scrollbar, no red
border, cards fully visible with breathing room.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 02:50:19 -05:00
parent e010822a74
commit 5eff3adc3c

View File

@@ -56,14 +56,16 @@ public:
// ---- Content-sized card: a stack of wallet cards, sized to the wallet count (up to // ---- Content-sized card: a stack of wallet cards, sized to the wallet count (up to
// kMaxVisibleRows, then the list scrolls). Computed from the KNOWN row count so the // kMaxVisibleRows, then the list scrolls). Computed from the KNOWN row count so the
// dialog stays fixed-height (no auto-height feedback that slid content off on a move). -- // dialog stays fixed-height (no auto-height feedback that slid content off on a move). --
const int kMaxVisibleRows = 6; const int kMaxVisibleRows = 7;
ImFont* nameFont = Type().body1(); ImFont* nameFont = Type().subtitle1();
ImFont* metaFont = Type().caption(); ImFont* metaFont = Type().caption();
const int visRows = std::min(std::max(1, (int)s_rows.size()), kMaxVisibleRows); const int visRows = std::min(std::max(1, (int)s_rows.size()), kMaxVisibleRows);
const float cardPadY = Layout::spacingSm(); const float cardPadY = Layout::spacingMd(); // roomier cards (was spacingSm)
const float walRowH = cardPadY * 2.0f + nameFont->LegacySize + Layout::spacingXs() + metaFont->LegacySize; const float walRowH = cardPadY * 2.0f + nameFont->LegacySize + Layout::spacingSm() + metaFont->LegacySize;
const float cardGap = Layout::spacingSm(); const float cardGap = Layout::spacingSm();
const float listH = visRows * walRowH + std::max(0, visRows - 1) * cardGap + 4.0f * dp; // ItemSpacing is zeroed inside the list child, so the content is exactly cards + inter-card
// gaps; a small bottom pad keeps the last card off the clip edge (no spurious scrollbar).
const float listH = visRows * walRowH + (float)std::max(0, visRows - 1) * cardGap + Layout::spacingSm();
const float ctrlRow = ImGui::GetFrameHeightWithSpacing(); const float ctrlRow = ImGui::GetFrameHeightWithSpacing();
const float capRow = Type().caption()->LegacySize + style.ItemSpacing.y; const float capRow = Type().caption()->LegacySize + style.ItemSpacing.y;
@@ -96,6 +98,7 @@ public:
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::BeginChild("##walletList", ImVec2(listW, listH), false); ImGui::BeginChild("##walletList", ImVec2(listW, listH), false);
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // inter-card gaps are explicit
ImDrawList* dl = ImGui::GetWindowDrawList(); ImDrawList* dl = ImGui::GetWindowDrawList();
const float rowW = ImGui::GetContentRegionAvail().x; const float rowW = ImGui::GetContentRegionAvail().x;
ImFont* icoFont = Type().iconSmall(); ImFont* icoFont = Type().iconSmall();
@@ -139,10 +142,10 @@ public:
float x = rMin.x + padX; float x = rMin.x + padX;
dl->AddText(icoFont, iconSz, ImVec2(x, midY - iconSz * 0.5f), dl->AddText(icoFont, iconSz, ImVec2(x, midY - iconSz * 0.5f),
isCurrent ? Success() : OnSurfaceMedium(), ICON_MD_ACCOUNT_BALANCE_WALLET); isCurrent ? Success() : OnSurfaceMedium(), ICON_MD_ACCOUNT_BALANCE_WALLET);
x += iconSz + Layout::spacingSm(); x += iconSz + Layout::spacingMd();
// Action-button slot on the right — reserve room so text truncates before it. // Action-button slot on the right — reserve room so text truncates before it.
const float btnW = 90.0f * dp, btnH = ImGui::GetFrameHeight(); const float btnW = 96.0f * dp, btnH = ImGui::GetFrameHeight();
const float btnX = rMax.x - padX - btnW; const float btnX = rMax.x - padX - btnW;
const float textR = btnX - Layout::spacingMd(); const float textR = btnX - Layout::spacingMd();
@@ -191,10 +194,14 @@ public:
} }
ImGui::PopID(); ImGui::PopID();
// Advance to the next card. // Register the card's footprint with a Dummy (not an interactive item, so nothing
ImGui::SetCursorScreenPos(ImVec2(rMin.x, rMax.y + cardGap)); // overlaps the action button's ID) and add an explicit gap between cards (ItemSpacing
ImGui::Dummy(ImVec2(rowW, 0)); // is 0, so no double spacing; the last card has no trailing gap).
ImGui::SetCursorScreenPos(rMin);
ImGui::Dummy(ImVec2(rowW, walRowH));
if (i + 1 < s_rows.size()) ImGui::Dummy(ImVec2(rowW, cardGap));
} }
ImGui::PopStyleVar(); // ItemSpacing
ImGui::EndChild(); ImGui::EndChild();
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));