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:
@@ -766,7 +766,7 @@ void RenderSettingsPage(App* app) {
|
||||
// don't dpi-scale these terms or the budget over-reserves and the combos shrink needlessly.
|
||||
float totalFixed = lblThemeW + lblLayoutW + lblLangW
|
||||
+ comboGap * 2 + Layout::spacingSm() + refreshBtnW;
|
||||
float comboW = std::max(80.0f, (contentW - totalFixed) / 3.0f);
|
||||
float comboW = std::min(std::max(80.0f, (contentW - totalFixed) / 3.0f), 300.0f * dp);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(TR("theme"));
|
||||
@@ -856,7 +856,7 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::TextUnformatted(TR("font_scale"));
|
||||
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW);
|
||||
float fontSliderW = std::min(std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW), 360.0f * dp);
|
||||
ImGui::SetNextItemWidth(fontSliderW);
|
||||
s_settingsState.font_scale = Layout::userFontScale();
|
||||
float prev_font_scale = s_settingsState.font_scale;
|
||||
@@ -956,7 +956,7 @@ void RenderSettingsPage(App* app) {
|
||||
// Row 1: Acrylic preset slider + Noise slider (side by side, labels above)
|
||||
float effCtrlMinW = S.drawElement("components.settings-page", "effects-input-min-width").size;
|
||||
float halfW = (contentW - Layout::spacingLg()) * 0.5f;
|
||||
float ctrlW = std::max(effCtrlMinW, halfW);
|
||||
float ctrlW = std::min(std::max(effCtrlMinW, halfW), 360.0f * dp);
|
||||
float baseX = ImGui::GetCursorScreenPos().x;
|
||||
float rightX = baseX + ctrlW + Layout::spacingLg();
|
||||
|
||||
@@ -1153,8 +1153,8 @@ void RenderSettingsPage(App* app) {
|
||||
{
|
||||
ImGui::PushFont(body2);
|
||||
ImGui::TextUnformatted(TR("font_scale"));
|
||||
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
||||
availWidth - pad * 2);
|
||||
float fontSliderW = std::min(std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
||||
availWidth - pad * 2), 360.0f * dp);
|
||||
ImGui::SetNextItemWidth(fontSliderW);
|
||||
s_settingsState.font_scale = Layout::userFontScale();
|
||||
float prev_font_scale = s_settingsState.font_scale;
|
||||
@@ -2218,7 +2218,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (fourAcross) {
|
||||
// fieldW = (contentW - labels - per-field label gaps - 3 inter-field gaps) / 4
|
||||
float inputTotal = contentW - labelsW - Layout::spacingXs() * 4 - spMd * 3;
|
||||
float inputW = std::max(60.0f, std::floor(inputTotal / 4.0f));
|
||||
float inputW = std::min(std::max(60.0f, std::floor(inputTotal / 4.0f)), 220.0f * dp);
|
||||
field(hostLbl, "##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host), inputW, false);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_host"));
|
||||
ImGui::SameLine(0, spMd);
|
||||
@@ -2453,8 +2453,8 @@ void RenderSettingsPage(App* app) {
|
||||
float halfW = (contentW - Layout::spacingLg()) * 0.5f;
|
||||
float lblTxW = ImGui::CalcTextSize("Transaction URL").x + Layout::spacingXs();
|
||||
float lblAddrW = ImGui::CalcTextSize("Address URL").x + Layout::spacingXs();
|
||||
float inputTxW = std::max(80.0f, halfW - lblTxW);
|
||||
float inputAddrW = std::max(80.0f, halfW - lblAddrW);
|
||||
float inputTxW = std::min(std::max(80.0f, halfW - lblTxW), 460.0f * dp);
|
||||
float inputAddrW = std::min(std::max(80.0f, halfW - lblAddrW), 460.0f * dp);
|
||||
|
||||
// Row start X (indent-inclusive) — the Address column is placed relative
|
||||
// to it, not to a fixed `pad`, so it lands correctly in the right column.
|
||||
|
||||
Reference in New Issue
Block a user