fix(ui): make HiDPI-overflowing containers scrollable — wizard, overlay dialogs, balance recent-tx

At font_scale 1.5 (dpiScale 1.5) three fixed, non-scrolling containers clipped
content off the bottom with no scroll escape:

- First-run wizard: the hand-drawn cards grow ~1.5x past the fixed window,
  pushing Continue / Encrypt & Continue / Skip off-screen (a setup blocker).
  Inject a wheel-driven scroll offset into the layout seed + a scroll indicator;
  gate the wheel on !IsPopupOpen + NoPopupHierarchy so an open combo popup does
  not scroll the wizard behind it. No-op at 1.0x.
- Overlay dialogs (BeginOverlayDialog): auto-height cards taller than the
  viewport (About, Request Payment) ran their footer off the bottom. Add a
  sticky per-open overflow flag that clamps the card to the viewport and makes
  the content child scrollable; short dialogs still center unchanged. Give the
  nested settings clear-history confirm its own idSuffix so it can't inherit the
  parent dialog's overflow state or collide on the child window id.
- Balance Recent Transactions: the dp-scaled address card evicted the recent-tx
  list off the non-scrolling tab host. Cap the card inside RenderSharedAddressList
  against the space that actually remains (minus a caller-provided reserve) so
  the section below stays on-screen — covers all 10 balance layouts.

Verified at font_scale 1.5 across full-node + Lite + Windows (ctest green) and an
adversarial diff review (two low-severity regressions found + fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 20:12:01 -05:00
co-authored by Claude Opus 4.8
parent e24ca015d1
commit 755cf22ad0
6 changed files with 96 additions and 24 deletions
+9 -1
View File
@@ -116,7 +116,7 @@ void RenderCompactHero(App* app, ImDrawList* dl, float availW, float hs, float v
// Render the shared address list section (used by all layouts)
void RenderSharedAddressList(App* app, float listH, float availW,
float glassRound, float hs, float vs) {
float glassRound, float hs, float vs, float reserveBelow) {
using namespace material;
const auto& S = schema::UISchema::instance();
const float dp = Layout::dpiScale();
@@ -225,6 +225,14 @@ void RenderSharedAddressList(App* app, float listH, float availW,
// ---- Glass panel container ----
float addrListH = listH;
// Cap the card to the space that actually remains here (measured AFTER the title + toolbar are laid
// out, so no chrome modelling is needed) minus what the caller reserves for the section below it
// (recent-tx). Without this, a fixed dp-scaled listH grows ~1.5x at high font scale and evicts the
// Recent Transactions list off the bottom of the fixed, non-scrolling tab host.
if (reserveBelow > 0.0f) {
float maxH = ImGui::GetContentRegionAvail().y - reserveBelow;
if (maxH < addrListH) addrListH = maxH;
}
if (addrListH < 40.0f * dp) addrListH = 40.0f * dp;
ImDrawList* dlPanel = ImGui::GetWindowDrawList();
+2 -1
View File
@@ -26,7 +26,8 @@ extern bool s_generating_z_address;
void UpdateBalanceLerp(App* app);
void RenderCompactHero(App* app, ImDrawList* dl, float availW, float hs, float vs,
float heroHeightOverride = -1.0f);
void RenderSharedAddressList(App* app, float listH, float availW, float glassRound, float hs, float vs);
void RenderSharedAddressList(App* app, float listH, float availW, float glassRound, float hs, float vs,
float reserveBelow = 0.0f);
void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float vs);
void RenderSyncBar(App* app, ImDrawList* dl, float vs);
+10 -10
View File
@@ -661,7 +661,7 @@ static void RenderBalanceClassic(App* app)
float addrH = (classicAddrH >= 0.0f) ? classicAddrH * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, contentAvail.x, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, contentAvail.x, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, contentAvail.x, hs, vs);
}
}
@@ -835,7 +835,7 @@ static void RenderBalanceDonut(App* app) {
float addrH = (donutAddrOverride >= 0.0f) ? donutAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1018,7 +1018,7 @@ static void RenderBalanceConsolidated(App* app) {
float addrH = (consAddrOverride >= 0.0f) ? consAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1175,7 +1175,7 @@ static void RenderBalanceDashboard(App* app) {
float addrH = (dashAddrOverride >= 0.0f) ? dashAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1354,7 +1354,7 @@ static void RenderBalanceVerticalStack(App* app) {
float addrH = (vstackAddrOverride >= 0.0f) ? vstackAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1549,7 +1549,7 @@ static void RenderBalanceVertical2x2(App* app) {
float addrH = (addrOverride >= 0.0f) ? addrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1731,7 +1731,7 @@ static void RenderBalanceShield(App* app) {
float addrH = (shieldAddrOverride >= 0.0f) ? shieldAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -1890,7 +1890,7 @@ static void RenderBalanceTimeline(App* app) {
float addrH = (tlAddrOverride >= 0.0f) ? tlAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -2081,7 +2081,7 @@ static void RenderBalanceTwoRow(App* app) {
float addrH = (twoRowAddrOverride >= 0.0f) ? twoRowAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
@@ -2174,7 +2174,7 @@ static void RenderBalanceMinimal(App* app) {
float addrH = (minAddrOverride >= 0.0f) ? minAddrOverride * dp
: ImGui::GetContentRegionAvail().y - recentReserve
- Layout::spacingXl() - Type().h6()->LegacySize - Layout::spacingMd();
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs);
RenderSharedAddressList(app, addrH, availW, glassRound, hs, vs, recentReserve);
RenderSharedRecentTx(app, recentReserve, availW, hs, vs);
}
+6 -1
View File
@@ -478,7 +478,12 @@ void RenderSettingsWindow(App* app, bool* p_open)
// Confirmation dialog
if (s_confirm_clear_ztx) {
if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f, 0.94f)) {
// Distinct idSuffix: this confirm renders nested inside (and the same frame as) the parent
// settings dialog, so it must not share the default ##OverlayDialogContent key — otherwise it
// inherits the parent's OverlayCardState (incl. the sticky overflow flag) and collides on the
// child window id.
if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f,
0.94f, 0.85f, "settings_clearztx")) {
material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.6f, 0.0f, 1.0f));
ImGui::Spacing();