feat(ui): width-responsiveness — content max-width cap + per-surface form/input clamps
Wide/ultrawide (1440-3440px) responsiveness was unhealthy: no page/content-level max-width cap existed, and every card/form/table derived width from raw GetContentRegionAvail().x with floor-only clamps, so surfaces stretched edge-to-edge (2000-3000px inputs, ballooning cards, giant grid cells, 2000px+ dead row-voids). Root cause: adopt the (previously dead-code) clamp helpers. - New Layout::kContentMaxWidth() (~1600dp, tunable via ui.toml [layout] content-max-width; <=0 disables). Cap ##ContentArea to it and center the column in wider windows — every tab derives from this child, so one change tames the app at wide widths. No-op below the cap (fills as before), so 1080p/1440p are unaffected. Per-surface upper-clamps (std::min(cap*dp, expr), floors preserved) where a single element is still too wide even within the capped column: - Settings: Theme/Layout/Language combos, the font-scale slider (~3000px -> 360dp), the effect sliders, Explorer URL and RPC credential fields. - Send / Receive: cap the compose / receive cards to a readable form width and center them (Indent(pad+offset) so the auto-layout fields align with the hand-drawn card); the recent-tx lists below keep the full column width. - Chat message bubbles + composer, mining pool URL/payout inputs + stats left/right split, contacts search, and the lite-network add-server row / server cards / status panel (capped + centered). - Wizard: vertically center the cards when they fit (was top-anchored, leaving a void on tall monitors), compensating the content-height measurement so it can't oscillate. The 1600 cap also subsumes the fixed-4-column balance grids (~400px cards) and the right-anchored row dead-gaps (voids shrink from ~2700px to ~800px), so those are left to the cap rather than blind column/row redesigns. Verified at 1024/1280 (and via a temporary 900dp cap to exercise the cap+center path, since the test display clamps to 1280) across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean). The true wide/ultrawide look and the 1600dp cap value still want eyes on a real wide monitor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -581,21 +581,34 @@ void RenderReceiveTab(App* app)
|
||||
// MAIN CARD — single glass panel (channel split like Send tab)
|
||||
// ================================================================
|
||||
{
|
||||
// Cap + center the card so the address/amount column stops stretching while the
|
||||
// QR plateaus. cardW never exceeds formW (only shrinks); leftover becomes margin.
|
||||
// The RECENT RECEIVED list below stays on the uncapped formW (handled separately).
|
||||
float cardDp = Layout::dpiScale();
|
||||
float cardW = std::min(formW, 860.0f * cardDp);
|
||||
float cardLeftX = ImGui::GetCursorScreenPos().x;
|
||||
float cardOffsetX = std::max(0.0f, (formW - cardW) * 0.5f);
|
||||
if (cardOffsetX > 0.0f)
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + cardOffsetX);
|
||||
|
||||
ImVec2 containerMin = ImGui::GetCursorScreenPos();
|
||||
float pad = Layout::spacingLg();
|
||||
float innerW = formW - pad * 2;
|
||||
float innerW = cardW - pad * 2;
|
||||
float innerGap = Layout::spacingLg();
|
||||
|
||||
// Channel split: content on ch1, glass background on ch0
|
||||
dl->ChannelsSplit(2);
|
||||
dl->ChannelsSetCurrent(1);
|
||||
|
||||
ImGui::Indent(pad);
|
||||
// Indent carries the centering offset too, so ImGui auto-layout content (the address
|
||||
// dropdown/inputs/chips) lands at containerMin.x + pad — matching the hand-drawn geometry
|
||||
// (glass panel / QR column) that keys off the offset containerMin.x.
|
||||
ImGui::Indent(pad + cardOffsetX);
|
||||
ImGui::Dummy(ImVec2(0, pad)); // top padding
|
||||
|
||||
// ---- ADDRESS DROPDOWN + QR CODE — side by side ----
|
||||
{
|
||||
float qrColW = innerW * schema::UI().drawElement("tabs.receive", "qr-col-width-ratio").size;
|
||||
float qrColW = std::min(innerW * schema::UI().drawElement("tabs.receive", "qr-col-width-ratio").size, 340.0f * cardDp);
|
||||
float colGap = Layout::spacingLg();
|
||||
float addrColW = innerW - qrColW - colGap;
|
||||
float qrColX = containerMin.x + pad + addrColW + colGap;
|
||||
@@ -876,7 +889,7 @@ void RenderReceiveTab(App* app)
|
||||
S.drawElement("tabs.receive", "action-btn-height").size * vScale);
|
||||
float footerH = innerGap + actionBtnH + pad;
|
||||
float currentCardH = ImGui::GetCursorScreenPos().y - containerMin.y;
|
||||
float targetCardH = std::min(Layout::mainCardTargetH(formW, vScale), recvCardCapH);
|
||||
float targetCardH = std::min(Layout::mainCardTargetH(cardW, vScale), recvCardCapH);
|
||||
float footerTopH = targetCardH - footerH;
|
||||
if (currentCardH < footerTopH) {
|
||||
ImGui::Dummy(ImVec2(0, footerTopH - currentCardH));
|
||||
@@ -887,7 +900,7 @@ void RenderReceiveTab(App* app)
|
||||
{
|
||||
ImVec2 divPos = ImGui::GetCursorScreenPos();
|
||||
dl->AddLine(ImVec2(containerMin.x + pad, divPos.y),
|
||||
ImVec2(containerMin.x + formW - pad, divPos.y),
|
||||
ImVec2(containerMin.x + cardW - pad, divPos.y),
|
||||
ImGui::GetColorU32(Divider()), S.drawElement("tabs.receive", "divider-thickness").size);
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, innerGap * 0.5f));
|
||||
@@ -967,24 +980,26 @@ void RenderReceiveTab(App* app)
|
||||
|
||||
// Bottom padding
|
||||
ImGui::Dummy(ImVec2(0, pad));
|
||||
ImGui::Unindent(pad);
|
||||
ImGui::Unindent(pad + cardOffsetX);
|
||||
|
||||
// Enforce shared card height (matches QR-driven target), capped so the reserved
|
||||
// RECENT RECEIVED slice below stays on-screen at HiDPI.
|
||||
{
|
||||
float currentCardH = ImGui::GetCursorScreenPos().y - containerMin.y;
|
||||
float targetCardH = std::min(Layout::mainCardTargetH(formW, vScale), recvCardCapH);
|
||||
float targetCardH = std::min(Layout::mainCardTargetH(cardW, vScale), recvCardCapH);
|
||||
if (currentCardH < targetCardH)
|
||||
ImGui::Dummy(ImVec2(0, targetCardH - currentCardH));
|
||||
}
|
||||
|
||||
// Draw glass panel background on channel 0
|
||||
ImVec2 containerMax(containerMin.x + formW, ImGui::GetCursorScreenPos().y);
|
||||
ImVec2 containerMax(containerMin.x + cardW, ImGui::GetCursorScreenPos().y);
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, containerMin, containerMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(containerMin.x, containerMax.y));
|
||||
// Restore the original (uncapped) left edge so RECENT RECEIVED below renders
|
||||
// full-width as-is, unaffected by the card's centering offset.
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardLeftX, containerMax.y));
|
||||
ImGui::Dummy(ImVec2(formW, 0));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user