diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 1a163f0..1e4b043 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -643,6 +643,27 @@ void RenderSettingsPage(App* app) { ImFont* body2 = Type().body2(); ImFont* sub1 = Type().subtitle1(); + // ==================================================================== + // Masonry: render the section cards in two columns when the page is wide + // enough. Left column: Theme, Wallet, Node & Security. Right column: + // Explorer, Chat & Contacts, About. Debug spans full width below. We shadow + // `availWidth` to the column width for the whole card region (every card + // reads it), and float the right column with Indent() (a one-shot cursor + // set can't hold a column across line-advances). Each card's own responsive + // gates collapse their internal columns at this narrower width. + // ==================================================================== + const float msFullWidth = availWidth; + const float msColGap = Layout::spacingXl(); + const bool msTwoCol = availWidth > 1000.0f * Layout::dpiScale(); + const float msColW = msTwoCol ? (msFullWidth - msColGap) * 0.5f : msFullWidth; + const ImVec2 msColTop = ImGui::GetCursorPos(); + float msLeftBottomY = 0.0f; + if (msTwoCol) { + availWidth = msColW; + inputW = std::max(S.drawElement("components.settings-page", "input-min-width").size, + availWidth - labelW - pad * 2); + } + // ==================================================================== // THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit // which breaks BeginCombo popup rendering in some ImGui versions) @@ -1037,8 +1058,11 @@ void RenderSettingsPage(App* app) { ImGui::TextUnformatted(TR("theme")); ImGui::SameLine(labelW); + // Reserve the real gaps (default ItemSpacing, not spacingSm) plus the + // custom-skin "*" marker so the Refresh button never spills past the edge. float themeComboW = std::max(S.drawElement("components.settings-page", "theme-combo-min-width").size, - availWidth - pad * 2 - labelW - refreshBtnW - Layout::spacingSm()); + availWidth - pad * 2 - labelW - refreshBtnW - ImGui::GetStyle().ItemSpacing.x + - (active_is_custom ? (ImGui::GetStyle().ItemSpacing.x + ImGui::CalcTextSize("*").x) : 0.0f)); ImGui::SetNextItemWidth(themeComboW); if (ImGui::BeginCombo("##Theme", active_preview.c_str())) { renderThemeComboPopup(); @@ -1377,11 +1401,11 @@ void RenderSettingsPage(App* app) { if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button"))) ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_shield_mining")); - ImGui::SameLine(0, btnSpacing); + if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button"))) ShieldDialog::show(ShieldDialog::Mode::MergeToAddress); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_merge")); - if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } + ImGui::SameLine(0, btnSpacing); if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) { s_settingsState.confirm_clear_ztx = true; } @@ -1498,6 +1522,9 @@ void RenderSettingsPage(App* app) { // also gets the RPC-backed encrypt/change/lock/remove block, includeRpcEncrypt). // `secX` is the row start X (== content-column left); `secColW` its width. auto renderSecuritySection = [&](float secX, float secColW, bool includeRpcEncrypt) { + // Narrow (two-column) card: the encrypt buttons, auto-lock and PIN + // controls can't all share one row, so wrap groups onto fresh rows. + const bool secNarrow = secColW < 740.0f * Layout::dpiScale(); // Encrypt / Change passphrase / Lock / Remove — RPC-backed, full-node only. // In lite the wallet's encryption controls live in the WALLET block above. if (includeRpcEncrypt) { @@ -1535,8 +1562,14 @@ void RenderSettingsPage(App* app) { ImGui::SameLine(0, Layout::spacingXs()); ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_unlocked")); } - // Remove Encryption button — trails the row. - ImGui::SameLine(0, Layout::spacingMd()); + // Remove Encryption button — trails the row, or wraps below + // when the column is too narrow for three buttons + status. + if (secNarrow) { + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y)); + } else { + ImGui::SameLine(0, Layout::spacingMd()); + } if (TactileButton(TR("settings_remove_encryption"), ImVec2(secBtnW, 0), S.resolveFont("button"))) app->showDecryptDialog(); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_remove_encrypt")); @@ -1558,7 +1591,7 @@ void RenderSettingsPage(App* app) { } // In a narrow (two-column) card the encrypt controls + auto-lock + PIN don't fit on // one row, so wrap the auto-lock/PIN group onto its own row at the section's left edge. - const bool secWrap = includeRpcEncrypt && secColW < 740.0f * Layout::dpiScale(); + const bool secWrap = includeRpcEncrypt && secNarrow; if (includeRpcEncrypt && !secWrap) { ImGui::SameLine(0, Layout::spacingLg()); } else { @@ -2338,7 +2371,17 @@ void RenderSettingsPage(App* app) { } } - ImGui::Dummy(ImVec2(0, gap)); + // End of the left column (Theme / Wallet / Node & Security). In two-column + // mode jump back to the top and indent so Explorer / Chat / About stack in + // the right column; otherwise just add the usual inter-card gap. + if (msTwoCol) { + msLeftBottomY = ImGui::GetCursorPosY(); + ImGui::SetCursorPosY(msColTop.y); + ImGui::Indent(msColW + msColGap); + ImGui::SetCursorPosX(msColTop.x + msColW + msColGap); + } else { + ImGui::Dummy(ImVec2(0, gap)); + } // ==================================================================== // EXPLORER & OPTIONS — full-width card @@ -2376,13 +2419,21 @@ void RenderSettingsPage(App* app) { ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - // Row 2: Checkboxes + Block Explorer button (on one line) + // Row 2: Checkboxes + Block Explorer button. Keep the two checkboxes + // side-by-side, but wrap the Block Explorer button onto its own row when + // it won't fit the (narrow, two-column) card — measured, so it's locale-safe. + const float expRowRight = ImGui::GetCursorScreenPos().x + contentW; ImGui::Checkbox(TrId("custom_fees", "custom_fees").c_str(), &s_settingsState.allow_custom_fees); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_custom_fees")); ImGui::SameLine(0, Layout::spacingLg()); ImGui::Checkbox(TrId("fetch_prices", "fetch_prices").c_str(), &s_settingsState.fetch_prices); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_fetch_prices")); - ImGui::SameLine(0, Layout::spacingLg()); + const float expBtnW = ImGui::CalcTextSize(TR("block_explorer")).x + + ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg(); + if (ImGui::GetItemRectMax().x + expBtnW <= expRowRight) + ImGui::SameLine(0, Layout::spacingLg()); + else + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); if (TactileButton(TR("block_explorer"), ImVec2(0, 0), S.resolveFont("button"))) { util::Platform::openUrl("https://explorer.dragonx.is"); } @@ -2488,7 +2539,11 @@ void RenderSettingsPage(App* app) { } { float fullContentW = availWidth - pad * 2; - float aboutBtnW = (fullContentW - Layout::spacingMd() * 3) / 4.0f; + // 2x2 grid in the narrow (two-column) card so labels don't clip; + // one 1x4 row at full width. + const bool aboutGrid = fullContentW < 720.0f * Layout::dpiScale(); + float aboutBtnW = aboutGrid ? (fullContentW - Layout::spacingMd()) / 2.0f + : (fullContentW - Layout::spacingMd() * 3) / 4.0f; if (TactileButton(TrId("website", "about_website").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) { util::Platform::openUrl("https://dragonx.is"); @@ -2499,7 +2554,7 @@ void RenderSettingsPage(App* app) { util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon/issues"); } if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_report_bug")); - ImGui::SameLine(0, Layout::spacingMd()); + if (aboutGrid) ImGui::Dummy(ImVec2(0, Layout::spacingXs())); else ImGui::SameLine(0, Layout::spacingMd()); if (TactileButton(TrId("save_settings", "about_save").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) { saveSettingsPageState(app->settings()); Notifications::instance().success("Settings saved"); @@ -2541,6 +2596,15 @@ void RenderSettingsPage(App* app) { ImGui::Dummy(ImVec2(availWidth, 0)); } + // End of the right column. Un-indent, drop below the taller of the two + // columns, and restore full width so Debug spans the page. + if (msTwoCol) { + ImGui::Unindent(msColW + msColGap); + const float msRightBottomY = ImGui::GetCursorPosY(); + ImGui::SetCursorPosX(msColTop.x); + ImGui::SetCursorPosY(std::max(msLeftBottomY, msRightBottomY)); + availWidth = msFullWidth; + } ImGui::Dummy(ImVec2(0, gap)); // ====================================================================