diff --git a/src/main.cpp b/src/main.cpp index ab98a71..f8cf9ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -957,6 +957,10 @@ int main(int argc, char* argv[]) // resizing on a 200% screen doesn't clobber the 100% size. std::map> savedSizeForScale; bool dpiResizePending = false; + bool fontScaleResizePending = false; + // Font-scale window reference: size at scale 1.0 (updated on user resize) + float fontScaleRefFS = 0.0f; + int fontScaleRefW = 0, fontScaleRefH = 0; // Track last known window size at the current DPI scale. // Updated on user-initiated resizes (not DPI auto-resizes). // When DISPLAY_SCALE_CHANGED fires, SDL_GetWindowSize() already @@ -1140,7 +1144,28 @@ int main(int argc, char* argv[]) // Poll remaining events SDL_Event event; + // Deferred font-scale commit: smooth visual update during Alt+scroll, + // atlas rebuild after scrolling stops (~200ms idle). + static Uint64 fontScaleCommitTick = 0; // 0 = nothing pending + while (SDL_PollEvent(&event)) { + // Alt + scroll wheel: smooth font scale adjustment + if (event.type == SDL_EVENT_MOUSE_WHEEL) { + SDL_Keymod mods = SDL_GetModState(); + if (mods & SDL_KMOD_ALT) { + float step = 0.05f * event.wheel.y; + float cur = dragonx::ui::Layout::userFontScale(); + float next = std::max(1.0f, std::min(1.5f, cur + step)); + if (next != cur) { + // Smooth preview — no atlas rebuild yet + dragonx::ui::Layout::setUserFontScaleVisual(next); + // Schedule atlas rebuild after scrolling stops + fontScaleCommitTick = SDL_GetTicks() + 200; + } + // Don't pass Alt+scroll to ImGui (would scroll windows) + continue; + } + } ImGui_ImplSDL3_ProcessEvent(&event); if (event.type == SDL_EVENT_QUIT) { @@ -1167,11 +1192,20 @@ int main(int argc, char* argv[]) bool isDpiResize = dpiResizePending || std::abs(actualScale - storedScale) > 0.01f; if (dpiResizePending) dpiResizePending = false; + bool isFSResize = fontScaleResizePending; + if (fontScaleResizePending) fontScaleResizePending = false; if (!isDpiResize) { int pct = (int)lroundf(storedScale * 100.0f); savedSizeForScale[pct] = {event.window.data1, event.window.data2}; lastKnownW = event.window.data1; lastKnownH = event.window.data2; + // User manually resized — update the font-scale reference + // so the next scale change is relative to this size. + if (!isFSResize) { + float fs = dragonx::ui::Layout::userFontScale(); + fontScaleRefW = (int)lroundf((float)event.window.data1 / fs); + fontScaleRefH = (int)lroundf((float)event.window.data2 / fs); + } } } // Window restored from minimized — trigger immediate data refresh @@ -1279,6 +1313,15 @@ int main(int argc, char* argv[]) // --- PerfLog: begin frame --- dragonx::util::PerfLog::instance().beginFrame(); + // Commit deferred font-scale change once scrolling has stopped. + if (fontScaleCommitTick != 0 && SDL_GetTicks() >= fontScaleCommitTick) { + fontScaleCommitTick = 0; + float fs = dragonx::ui::Layout::userFontScale(); + dragonx::ui::Layout::setUserFontScale(fs); + app.settings()->setFontScale(fs); + app.settings()->save(); + } + // Pre-frame: font atlas rebuilds and schema hot-reload must // happen BEFORE NewFrame() because NewFrame() caches font ptrs. app.preFrame(); @@ -1294,25 +1337,24 @@ int main(int argc, char* argv[]) } // If font scale changed, resize window proportionally. - // anchorW/H = the window size at the moment the anchor was set. - // anchorFS = the font scale at that moment. - // target = anchorW * curFS / anchorFS (symmetric, no drift). + // Reference size (at scale 1.0) is updated on user-initiated resizes + // so the next scale change is relative to the current window, not a + // stale snapshot. target = ref * curFS — no rounding drift. { - static float anchorFS = 0.0f; - static int anchorW = 0, anchorH = 0; float curFS = dragonx::ui::Layout::userFontScale(); - if (anchorFS < 0.001f) { - // First frame: the current window IS the reference for - // whatever font scale is loaded — no resize. - anchorFS = curFS; - SDL_GetWindowSize(window, &anchorW, &anchorH); + if (fontScaleRefFS < 0.001f) { + // First frame — record reference, no resize. + fontScaleRefFS = curFS; + int w, h; + SDL_GetWindowSize(window, &w, &h); + fontScaleRefW = (int)lroundf((float)w / curFS); + fontScaleRefH = (int)lroundf((float)h / curFS); } - if (std::fabs(curFS - anchorFS) > 0.001f) { - float ratio = curFS / anchorFS; - int newW = (int)lroundf((float)anchorW * ratio); - int newH = (int)lroundf((float)anchorH * ratio); + if (std::fabs(curFS - fontScaleRefFS) > 0.001f) { + int newW = (int)lroundf((float)fontScaleRefW * curFS); + int newH = (int)lroundf((float)fontScaleRefH * curFS); // Clamp to display work area SDL_DisplayID did = SDL_GetDisplayForWindow(window); @@ -1325,21 +1367,15 @@ int main(int argc, char* argv[]) } float hwDpi = dragonx::ui::Layout::rawDpiScale(); - // Update minimum size BEFORE resizing so the window can - // actually shrink when the font scale decreases. SDL_SetWindowMinimumSize(window, - (int)(1024 * hwDpi * curFS), - (int)(720 * hwDpi * curFS)); + (int)(1024 * hwDpi), + (int)(720 * hwDpi)); + fontScaleResizePending = true; SDL_SetWindowSize(window, newW, newH); lastKnownW = newW; lastKnownH = newH; - // Update anchor so we don't re-fire every frame. - // The new anchor becomes the current size/scale so - // subsequent slider movements are relative to here. - anchorW = newW; - anchorH = newH; - anchorFS = curFS; + fontScaleRefFS = curFS; } } diff --git a/src/ui/layout.h b/src/ui/layout.h index de50aaa..cf584e8 100644 --- a/src/ui/layout.h +++ b/src/ui/layout.h @@ -70,8 +70,10 @@ inline void setFontAtlasScale(float v) { detail::fontAtlasScaleRef() = v; } */ inline void setUserFontScale(float v) { v = std::max(1.0f, std::min(1.5f, v)); - if (v != detail::userFontScaleRef()) { - detail::userFontScaleRef() = v; + detail::userFontScaleRef() = v; + // Compare against the atlas scale, not the live ref — setUserFontScaleVisual + // may have already updated the ref during slider drag. + if (v != detail::fontAtlasScaleRef()) { detail::fontReloadNeededRef() = true; } } diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index c75004b..9f20464 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -916,6 +916,8 @@ void RenderSettingsPage(App* app) { float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, availWidth - pad * 2); ImGui::SetNextItemWidth(fontSliderW); + // Sync from Layout so Alt+scroll hotkey changes are reflected + sp_font_scale = Layout::userFontScale(); float prev_font_scale = sp_font_scale; { char fs_fmt[16]; @@ -923,10 +925,11 @@ void RenderSettingsPage(App* app) { ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt, ImGuiSliderFlags_AlwaysClamp); } - // Smooth continuous scaling while dragging. + // Snap to 0.05 increments for consistent stepping. // Visual scaling uses FontScaleMain (no atlas rebuild), // atlas rebuild is deferred to slider release for crisp text. - sp_font_scale = std::max(1.0f, std::min(1.5f, sp_font_scale)); + sp_font_scale = std::max(1.0f, std::min(1.5f, + std::round(sp_font_scale * 20.0f) / 20.0f)); if (sp_font_scale != prev_font_scale) { // While dragging: update layout scale without atlas rebuild Layout::setUserFontScaleVisual(sp_font_scale); diff --git a/src/ui/windows/balance_tab.cpp b/src/ui/windows/balance_tab.cpp index 26428d8..73640f6 100644 --- a/src/ui/windows/balance_tab.cpp +++ b/src/ui/windows/balance_tab.cpp @@ -388,9 +388,10 @@ static void RenderBalanceClassic(App* app) float classicCardH = S.drawElement("tabs.balance.classic", "card-height").size; float cardH; if (classicCardH >= 0.0f) { - cardH = classicCardH; // explicit override from ui.toml + // TOML override scaled by dp so it grows with DPI + user font scale + cardH = std::max(classicCardH * dp, marketContentH); } else { - float minH = S.drawElement("tabs.balance.classic", "card-min-height").sizeOr(70.0f); + float minH = S.drawElement("tabs.balance.classic", "card-min-height").sizeOr(70.0f) * dp; cardH = std::max(StatCardHeight(vs, minH), marketContentH); } @@ -814,7 +815,7 @@ static void RenderBalanceClassic(App* app) float classicAddrH = S.drawElement("tabs.balance.classic", "address-table-height").size; float addrListH; if (classicAddrH >= 0.0f) { - addrListH = classicAddrH; // explicit override from ui.toml + addrListH = classicAddrH * dp; // scaled for DPI + font scale } else { addrListH = ImGui::GetContentRegionAvail().y - recentTxReserve; } @@ -1881,14 +1882,20 @@ static void RenderBalanceDonut(App* app) { // --- Donut + legend panel --- { + ImFont* donutOv = Type().overline(); + ImFont* donutCap = Type().caption(); + // Font-content floor: legend needs overline + 3 caption lines + spacing + float donutFontFloor = Layout::spacingLg() * 2 + + donutOv->LegacySize + Layout::spacingMd() + + donutCap->LegacySize * 3 + Layout::spacingSm() * 3; float donutCardH = S.drawElement("tabs.balance.donut", "card-height").size; float panelH; if (donutCardH >= 0.0f) { - panelH = donutCardH; // explicit override from ui.toml + panelH = std::max(donutCardH * dp, donutFontFloor); } else { - panelH = std::max( - S.drawElement("tabs.balance.donut", "panel-min-height").sizeOr(80.0f), - contentAvail.y * S.drawElement("tabs.balance.donut", "panel-height-ratio").sizeOr(0.20f)); + panelH = std::max({donutFontFloor, + S.drawElement("tabs.balance.donut", "panel-min-height").sizeOr(80.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.donut", "panel-height-ratio").sizeOr(0.20f)}); } ImVec2 panelMin = ImGui::GetCursorScreenPos(); ImVec2 panelMax(panelMin.x + availW, panelMin.y + panelH); @@ -1985,7 +1992,7 @@ static void RenderBalanceDonut(App* app) { // --- Shared address list + recent tx --- float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float donutAddrOverride = S.drawElement("tabs.balance.donut", "address-table-height").size; - float addrH = (donutAddrOverride >= 0.0f) ? donutAddrOverride + 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); @@ -2016,14 +2023,25 @@ static void RenderBalanceConsolidated(App* app) { if (consTopMargin >= 0.0f) ImGui::Dummy(ImVec2(0, consTopMargin)); + ImFont* heroFont = Type().h2(); + ImFont* sub1 = Type().subtitle1(); + ImFont* capFont = Type().caption(); + ImFont* ovFont = Type().overline(); + float consPadOverride = S.drawElement("tabs.balance.consolidated", "card-padding").size; + float pad = (consPadOverride >= 0.0f) ? consPadOverride : Layout::spacingLg(); + // Font-content floor: pad + overline + hero + caption + subtitle + pad + float consFontFloor = pad + ovFont->LegacySize + Layout::spacingSm() + + heroFont->LegacySize + Layout::spacingSm() + + capFont->LegacySize + Layout::spacingSm() + + sub1->LegacySize + pad; float consCardH = S.drawElement("tabs.balance.consolidated", "card-height").size; float cardH; if (consCardH >= 0.0f) { - cardH = consCardH; // explicit override from ui.toml + cardH = std::max(consCardH * dp, consFontFloor); } else { - cardH = std::max( - S.drawElement("tabs.balance.consolidated", "card-min-height").sizeOr(90.0f), - contentAvail.y * S.drawElement("tabs.balance.consolidated", "card-height-ratio").sizeOr(0.22f)); + cardH = std::max({consFontFloor, + S.drawElement("tabs.balance.consolidated", "card-min-height").sizeOr(90.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.consolidated", "card-height-ratio").sizeOr(0.22f)}); } ImVec2 cardMin = ImGui::GetCursorScreenPos(); ImVec2 cardMax(cardMin.x + availW, cardMin.y + cardH); @@ -2031,17 +2049,11 @@ static void RenderBalanceConsolidated(App* app) { spec.rounding = glassRound; DrawGlassPanel(dl, cardMin, cardMax, spec); - float consPadOverride = S.drawElement("tabs.balance.consolidated", "card-padding").size; - float pad = (consPadOverride >= 0.0f) ? consPadOverride : Layout::spacingLg(); float cx = cardMin.x + pad; float cy = cardMin.y + pad; // Coin logo ImTextureID logoTex = app->getCoinLogoTexture(); - ImFont* heroFont = Type().h2(); - ImFont* sub1 = Type().subtitle1(); - ImFont* capFont = Type().caption(); - ImFont* ovFont = Type().overline(); float logoSz = heroFont->LegacySize + capFont->LegacySize + 4.0f * dp; if (logoTex != 0) { @@ -2157,7 +2169,7 @@ static void RenderBalanceConsolidated(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float consAddrOverride = S.drawElement("tabs.balance.consolidated", "address-table-height").size; - float addrH = (consAddrOverride >= 0.0f) ? consAddrOverride + 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); @@ -2199,22 +2211,28 @@ static void RenderBalanceDashboard(App* app) { : (int)S.drawElement("tabs.balance.dashboard", "tile-num-cols").sizeOr(4.0f); int numRows = (numCols == 2) ? 2 : 1; float tileW = (availW - (numCols - 1) * cGap) / numCols; + ImFont* ovFont = Type().overline(); + ImFont* sub1 = Type().subtitle1(); + ImFont* capFont = Type().caption(); + // Font-content floor: pad + overline + subtitle1 + caption + pad + float dashFontFloor = Layout::spacingLg() + + ovFont->LegacySize + Layout::spacingSm() + + sub1->LegacySize + Layout::spacingSm() + + capFont->LegacySize + + Layout::spacingLg(); float dashCardH = S.drawElement("tabs.balance.dashboard", "card-height").size; float tileH; if (dashCardH >= 0.0f) { - tileH = dashCardH; // explicit override from ui.toml + tileH = std::max(dashCardH * dp, dashFontFloor); } else { - tileH = std::max( - S.drawElement("tabs.balance.dashboard", "tile-min-height").sizeOr(70.0f), - contentAvail.y * S.drawElement("tabs.balance.dashboard", "tile-height-ratio").sizeOr(0.16f) / numRows); + tileH = std::max({dashFontFloor, + S.drawElement("tabs.balance.dashboard", "tile-min-height").sizeOr(70.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.dashboard", "tile-height-ratio").sizeOr(0.16f) / numRows}); } ImVec2 origin = ImGui::GetCursorScreenPos(); GlassPanelSpec tileSpec; tileSpec.rounding = glassRound; - ImFont* ovFont = Type().overline(); - ImFont* sub1 = Type().subtitle1(); - ImFont* capFont = Type().caption(); struct TileInfo { const char* label; @@ -2299,7 +2317,7 @@ static void RenderBalanceDashboard(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float dashAddrOverride = S.drawElement("tabs.balance.dashboard", "address-table-height").size; - float addrH = (dashAddrOverride >= 0.0f) ? dashAddrOverride + 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); @@ -2329,23 +2347,28 @@ static void RenderBalanceVerticalStack(App* app) { if (vstackTopMargin >= 0.0f) ImGui::Dummy(ImVec2(0, vstackTopMargin)); - float vstackCardH = S.drawElement("tabs.balance.vertical-stack", "card-height").size; - float stackH; - if (vstackCardH >= 0.0f) { - stackH = vstackCardH; // explicit override from ui.toml - } else { - stackH = std::max( - S.drawElement("tabs.balance.vertical-stack", "stack-min-height").sizeOr(80.0f), - contentAvail.y * S.drawElement("tabs.balance.vertical-stack", "stack-height-ratio").sizeOr(0.16f)); - } - float rowGap = S.drawElement("tabs.balance.vertical-stack", "row-gap").sizeOr(2.0f); - float rowH = (stackH - 3 * rowGap) / 4.0f; - float rowMinH = S.drawElement("tabs.balance.vertical-stack", "row-min-height").sizeOr(20.0f); - if (rowH < rowMinH) rowH = rowMinH; - ImFont* capFont = Type().caption(); ImFont* body2 = Type().body2(); ImFont* sub1 = Type().subtitle1(); + // Font-content floor per row: icon + label + value must fit + float vstackRowFontFloor = std::max(body2->LegacySize, capFont->LegacySize) + + Layout::spacingSm() * 2; + float rowGap = S.drawElement("tabs.balance.vertical-stack", "row-gap").sizeOr(2.0f); + float vstackFontFloor = vstackRowFontFloor * 4 + rowGap * 3; + float vstackCardH = S.drawElement("tabs.balance.vertical-stack", "card-height").size; + float stackH; + if (vstackCardH >= 0.0f) { + stackH = std::max(vstackCardH * dp, vstackFontFloor); + } else { + stackH = std::max({vstackFontFloor, + S.drawElement("tabs.balance.vertical-stack", "stack-min-height").sizeOr(80.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.vertical-stack", "stack-height-ratio").sizeOr(0.16f)}); + } + float rowH = (stackH - 3 * rowGap) / 4.0f; + float rowMinH = std::max( + S.drawElement("tabs.balance.vertical-stack", "row-min-height").sizeOr(20.0f) * dp, + vstackRowFontFloor); + if (rowH < rowMinH) rowH = rowMinH; float total = (float)s_dispTotal; float shieldRatio = (total > 1e-9f) ? (float)(s_dispShielded / total) : 0.5f; @@ -2473,7 +2496,7 @@ static void RenderBalanceVerticalStack(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float vstackAddrOverride = S.drawElement("tabs.balance.vertical-stack", "address-table-height").size; - float addrH = (vstackAddrOverride >= 0.0f) ? vstackAddrOverride + 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); @@ -2505,26 +2528,30 @@ static void RenderBalanceVertical2x2(App* app) { if (topMargin >= 0.0f) ImGui::Dummy(ImVec2(0, topMargin)); + ImFont* capFont = Type().caption(); + ImFont* iconFont = Type().iconSmall(); + // Font-content floor per row: caption text + vertical padding + float v2x2RowFontFloor = capFont->LegacySize + Layout::spacingSm() * 2; + float rowGap = S.drawElement(cfgSec, "row-gap").sizeOr(2.0f); + float colGap = S.drawElement(cfgSec, "col-gap").sizeOr(8.0f); + float v2x2FontFloor = v2x2RowFontFloor * 2 + rowGap; float cardHOverride = S.drawElement(cfgSec, "card-height").size; float stackH; if (cardHOverride >= 0.0f) { - stackH = cardHOverride; + stackH = std::max(cardHOverride * dp, v2x2FontFloor); } else { - stackH = std::max( - S.drawElement(cfgSec, "stack-min-height").sizeOr(60.0f), - contentAvail.y * S.drawElement(cfgSec, "stack-height-ratio").sizeOr(0.12f)); + stackH = std::max({v2x2FontFloor, + S.drawElement(cfgSec, "stack-min-height").sizeOr(60.0f) * dp, + contentAvail.y * S.drawElement(cfgSec, "stack-height-ratio").sizeOr(0.12f)}); } - float rowGap = S.drawElement(cfgSec, "row-gap").sizeOr(2.0f); - float colGap = S.drawElement(cfgSec, "col-gap").sizeOr(8.0f); float rowH = (stackH - rowGap) / 2.0f; - float rowMinH = S.drawElement(cfgSec, "row-min-height").sizeOr(24.0f); + float rowMinH = std::max( + S.drawElement(cfgSec, "row-min-height").sizeOr(24.0f) * dp, + v2x2RowFontFloor); if (rowH < rowMinH) rowH = rowMinH; float colW = (availW - colGap) / 2.0f; - ImFont* capFont = Type().caption(); - ImFont* iconFont = Type().iconSmall(); - float padOverride = S.drawElement(cfgSec, "card-padding").size; float rowPad = (padOverride >= 0.0f) ? padOverride : Layout::spacingLg(); float rowBgAlpha = S.drawElement(cfgSec, "row-bg-alpha").sizeOr(8.0f); @@ -2664,7 +2691,7 @@ static void RenderBalanceVertical2x2(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float addrOverride = S.drawElement(cfgSec, "address-table-height").size; - float addrH = (addrOverride >= 0.0f) ? addrOverride + 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); @@ -2722,14 +2749,20 @@ static void RenderBalanceShield(App* app) { // Shield gauge panel { + ImFont* shieldCap = Type().caption(); + ImFont* shieldSub1 = Type().subtitle1(); + // Font-content floor: gauge area + legend text (subtitle + 2 captions) + float shieldFontFloor = Layout::spacingLg() * 2 + + shieldSub1->LegacySize + Layout::spacingSm() + + shieldCap->LegacySize * 2 + Layout::spacingSm() * 2; float shieldCardH = S.drawElement("tabs.balance.shield", "card-height").size; float gaugeH; if (shieldCardH >= 0.0f) { - gaugeH = shieldCardH; // explicit override from ui.toml + gaugeH = std::max(shieldCardH * dp, shieldFontFloor); } else { - gaugeH = std::max( - S.drawElement("tabs.balance.shield", "gauge-min-height").sizeOr(80.0f), - contentAvail.y * S.drawElement("tabs.balance.shield", "gauge-height-ratio").sizeOr(0.18f)); + gaugeH = std::max({shieldFontFloor, + S.drawElement("tabs.balance.shield", "gauge-min-height").sizeOr(80.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.shield", "gauge-height-ratio").sizeOr(0.18f)}); } ImVec2 panelMin = ImGui::GetCursorScreenPos(); ImVec2 panelMax(panelMin.x + availW, panelMin.y + gaugeH); @@ -2834,7 +2867,7 @@ static void RenderBalanceShield(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float shieldAddrOverride = S.drawElement("tabs.balance.shield", "address-table-height").size; - float addrH = (shieldAddrOverride >= 0.0f) ? shieldAddrOverride + 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); @@ -2895,10 +2928,10 @@ static void RenderBalanceTimeline(App* app) { float tlChartH = S.drawElement("tabs.balance.timeline", "chart-height").size; float chartH; if (tlChartH >= 0.0f) { - chartH = tlChartH; // explicit override from ui.toml + chartH = tlChartH * dp; // scaled by dp for DPI + font scale } else { chartH = std::max( - S.drawElement("tabs.balance.timeline", "chart-min-height").sizeOr(80.0f), + S.drawElement("tabs.balance.timeline", "chart-min-height").sizeOr(80.0f) * dp, contentAvail.y * S.drawElement("tabs.balance.timeline", "chart-height-ratio").sizeOr(0.20f)); } ImVec2 chartMin = ImGui::GetCursorScreenPos(); @@ -2935,21 +2968,25 @@ static void RenderBalanceTimeline(App* app) { // Compact 3 summary cards ImGui::Dummy(ImVec2(0, cGap)); { + ImFont* ovFont = Type().overline(); + ImFont* capFont = Type().caption(); + // Font-content floor: pad + overline + gap + caption + pad + float tlPadVal = S.drawElement("tabs.balance.timeline", "card-padding").size; + float tlPad = (tlPadVal >= 0.0f) ? tlPadVal : Layout::spacingXs(); + float tlFontFloor = tlPad + ovFont->LegacySize + 2.0f * dp + capFont->LegacySize + tlPad; float tlSummaryH = S.drawElement("tabs.balance.timeline", "summary-card-height").size; float cardH; if (tlSummaryH >= 0.0f) { - cardH = tlSummaryH; // explicit override from ui.toml + cardH = std::max(tlSummaryH * dp, tlFontFloor); } else { - cardH = std::max( - S.drawElement("tabs.balance.timeline", "summary-min-height").sizeOr(44.0f), - contentAvail.y * S.drawElement("tabs.balance.timeline", "summary-height-ratio").sizeOr(0.08f)); + cardH = std::max({tlFontFloor, + S.drawElement("tabs.balance.timeline", "summary-min-height").sizeOr(44.0f) * dp, + contentAvail.y * S.drawElement("tabs.balance.timeline", "summary-height-ratio").sizeOr(0.08f)}); } float cardW = (availW - 2 * cGap) / 3.0f; ImVec2 origin = ImGui::GetCursorScreenPos(); GlassPanelSpec spec; spec.rounding = glassRound; - ImFont* ovFont = Type().overline(); - ImFont* capFont = Type().caption(); struct SumCard { const char* label; ImU32 col; double val; bool isMoney; }; SumCard cards[3] = { @@ -2983,7 +3020,7 @@ static void RenderBalanceTimeline(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float tlAddrOverride = S.drawElement("tabs.balance.timeline", "address-table-height").size; - float addrH = (tlAddrOverride >= 0.0f) ? tlAddrOverride + 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); @@ -3055,14 +3092,17 @@ static void RenderBalanceTwoRow(App* app) { // Row 2: 3 mini-cards inline { + ImFont* twoRowCap = Type().caption(); + // Font-content floor: caption centered + vertical padding + float twoRowFontFloor = twoRowCap->LegacySize + Layout::spacingSm() * 2; float twoRowCardH = S.drawElement("tabs.balance.two-row", "card-height").size; float miniH; if (twoRowCardH >= 0.0f) { - miniH = twoRowCardH; // explicit override from ui.toml + miniH = std::max(twoRowCardH * dp, twoRowFontFloor); } else { - miniH = std::max( - S.drawElement("tabs.balance.two-row", "mini-min-height").sizeOr(28.0f), - S.drawElement("tabs.balance.two-row", "mini-base-height").sizeOr(36.0f) * vs); + miniH = std::max({twoRowFontFloor, + S.drawElement("tabs.balance.two-row", "mini-min-height").sizeOr(28.0f) * dp, + S.drawElement("tabs.balance.two-row", "mini-base-height").sizeOr(36.0f) * vs}); } float miniW = (availW - 2 * cGap) / 3.0f; ImVec2 origin = ImGui::GetCursorScreenPos(); @@ -3170,7 +3210,7 @@ static void RenderBalanceTwoRow(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float twoRowAddrOverride = S.drawElement("tabs.balance.two-row", "address-table-height").size; - float addrH = (twoRowAddrOverride >= 0.0f) ? twoRowAddrOverride + 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); @@ -3191,6 +3231,7 @@ static void RenderBalanceMinimal(App* app) { float hs = Layout::hScale(availW); float vs = Layout::vScale(contentAvail.y); float glassRound = Layout::glassRounding(); + const float dp = Layout::dpiScale(); ImDrawList* dl = ImGui::GetWindowDrawList(); char buf[64]; @@ -3262,7 +3303,7 @@ static void RenderBalanceMinimal(App* app) { float recentReserve = contentAvail.y * S.drawElement("tabs.balance", "recent-tx-reserve-ratio").sizeOr(0.18f); float minAddrOverride = S.drawElement("tabs.balance.minimal", "address-table-height").size; - float addrH = (minAddrOverride >= 0.0f) ? minAddrOverride + 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);