From cf520fdf404e3e301d4a3877f7f7b9caba7ccd12 Mon Sep 17 00:00:00 2001 From: dan_s Date: Wed, 11 Mar 2026 01:38:40 -0500 Subject: [PATCH] ui: reorganize settings page with collapsible sections - Rename APPEARANCE section to THEME & LANGUAGE - Move font scale slider out of effects into main section - Collapse visual effects into "Advanced Effects..." toggle - Collapse wallet tools into "Tools & Actions..." toggle - Remove redundant Tools & Actions divider/section from wallet card - Add i18n strings: theme_language, advanced_effects, tools_actions --- src/ui/pages/settings_page.cpp | 347 +++++++++++++++++++-------------- src/util/i18n.cpp | 3 + 2 files changed, 200 insertions(+), 150 deletions(-) diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 579b674..aa7329b 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -122,6 +122,8 @@ static bool sp_verbose_logging = false; static std::set sp_debug_categories; static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted static bool sp_debug_expanded = false; // collapsible card state +static bool sp_effects_expanded = false; // "Advanced Effects..." toggle +static bool sp_tools_expanded = false; // "Tools & Actions..." toggle static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history // (APPEARANCE card now uses ChannelsSplit like all other cards) @@ -377,11 +379,11 @@ void RenderSettingsPage(App* app) { ImFont* sub1 = Type().subtitle1(); // ==================================================================== - // APPEARANCE — card (draw-first approach; avoids ChannelsSplit which - // breaks BeginCombo popup rendering in some ImGui versions) + // THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit + // which breaks BeginCombo popup rendering in some ImGui versions) // ==================================================================== { - Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("appearance")); + Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language")); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); @@ -561,9 +563,59 @@ void RenderSettingsPage(App* app) { ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - // --- Visual Effects (checkboxes on one row, Quality+Blur paired) --- + // --- Font Scale slider (always visible) --- { ImGui::PushFont(body2); + ImGui::TextUnformatted(TR("font_scale")); + float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW); + ImGui::SetNextItemWidth(fontSliderW); + sp_font_scale = Layout::userFontScale(); + float prev_font_scale = sp_font_scale; + { + char fs_fmt[16]; + snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale); + ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt, + ImGuiSliderFlags_AlwaysClamp); + } + 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) + Layout::setUserFontScaleVisual(sp_font_scale); + if (ImGui::IsItemDeactivatedAfterEdit()) { + Layout::setUserFontScale(sp_font_scale); + saveSettingsPageState(app->settings()); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale")); + ImGui::PopFont(); + } + + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // --- Collapsible: Advanced Effects... --- + { + const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE; + ImGui::PushFont(body2); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f)); + { + ImVec2 hdrPos = ImGui::GetCursorScreenPos(); + if (ImGui::Button("##EffectsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) { + sp_effects_expanded = !sp_effects_expanded; + } + float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f; + dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects")); + ImFont* iconFont = Type().iconSmall(); + if (!iconFont) iconFont = body2; + float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x; + dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow); + } + ImGui::PopStyleColor(3); + ImGui::PopFont(); + } + + if (sp_effects_expanded) { + ImGui::PushFont(body2); // Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) { @@ -603,7 +655,6 @@ void RenderSettingsPage(App* app) { } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec")); - // Simple background is lightweight — always interactive, even in low-spec mode ImGui::SameLine(0, Layout::spacingLg()); if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &sp_gradient_background)) { schema::SkinManager::instance().setGradientMode(sp_gradient_background); @@ -635,12 +686,10 @@ void RenderSettingsPage(App* app) { float baseX = ImGui::GetCursorScreenPos().x; float rightX = baseX + ctrlW + Layout::spacingLg(); - // Acrylic label + slider (left column) ImGui::TextUnformatted(TR("acrylic")); float row1Y = ImGui::GetCursorScreenPos().y; ImGui::SetNextItemWidth(ctrlW); { - // Build display format: "Off" at zero, percentage otherwise char blur_fmt[16]; if (sp_blur_amount < 0.01f) snprintf(blur_fmt, sizeof(blur_fmt), "Off"); @@ -648,7 +697,6 @@ void RenderSettingsPage(App* app) { snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f); if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt, ImGuiSliderFlags_AlwaysClamp)) { - // Snap to off when dragged near 0% if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f; sp_acrylic_enabled = (sp_blur_amount > 0.001f); effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount); @@ -658,7 +706,6 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur")); float afterRow1Y = ImGui::GetCursorScreenPos().y; - // Noise label + slider (right column, same row) float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y; ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH)); ImGui::TextUnformatted(TR("noise")); @@ -678,10 +725,8 @@ void RenderSettingsPage(App* app) { } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise")); - // Reset cursor to left column, past row 1 ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y)); - // Row 2: UI Opacity + Window Opacity (labels above) ImGui::TextUnformatted(TR("ui_opacity")); float row2Y = ImGui::GetCursorScreenPos().y; ImGui::SetNextItemWidth(ctrlW); @@ -697,7 +742,6 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity")); float afterRow2Y = ImGui::GetCursorScreenPos().y; - // Window label + slider (right column, same row) ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH)); ImGui::TextUnformatted(TR("window_opacity")); ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y)); @@ -712,12 +756,11 @@ void RenderSettingsPage(App* app) { } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity")); - // Reset cursor to left column, past row 2 ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y)); ImGui::EndDisabled(); // low-spec ImGui::PopFont(); - } + } // sp_effects_expanded } else { // ============================================================ // Narrow: stacked combos + 2-column effects (original layout) @@ -810,11 +853,62 @@ void RenderSettingsPage(App* app) { ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - // --- Visual Effects (checkboxes + controls) --- + // --- Font Scale slider (always visible) --- { 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); + ImGui::SetNextItemWidth(fontSliderW); + sp_font_scale = Layout::userFontScale(); + float prev_font_scale = sp_font_scale; + { + char fs_fmt[16]; + snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale); + ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt, + ImGuiSliderFlags_AlwaysClamp); + } + 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) + Layout::setUserFontScaleVisual(sp_font_scale); + if (ImGui::IsItemDeactivatedAfterEdit()) { + Layout::setUserFontScale(sp_font_scale); + saveSettingsPageState(app->settings()); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale")); + ImGui::PopFont(); + } + + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // --- Collapsible: Advanced Effects... --- + { + const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE; + ImGui::PushFont(body2); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f)); + { + float narrowContentW = availWidth - pad * 2; + ImVec2 hdrPos = ImGui::GetCursorScreenPos(); + if (ImGui::Button("##EffectsToggleN", ImVec2(narrowContentW, ImGui::GetFrameHeight()))) { + sp_effects_expanded = !sp_effects_expanded; + } + float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f; + dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects")); + ImFont* iconFont = Type().iconSmall(); + if (!iconFont) iconFont = body2; + float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x; + dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + narrowContentW - arrowW, textY), OnSurfaceMedium(), arrow); + } + ImGui::PopStyleColor(3); + ImGui::PopFont(); + } + + if (sp_effects_expanded) { + ImGui::PushFont(body2); - // Checkbox row 1: Low-spec if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) { effects::setLowSpecMode(sp_low_spec_mode); if (sp_low_spec_mode) { @@ -852,7 +946,6 @@ void RenderSettingsPage(App* app) { } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec")); - // Simple background is lightweight — always interactive, even in low-spec mode if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &sp_gradient_background)) { schema::SkinManager::instance().setGradientMode(sp_gradient_background); saveSettingsPageState(app->settings()); @@ -861,7 +954,6 @@ void RenderSettingsPage(App* app) { ImGui::BeginDisabled(sp_low_spec_mode); - // Checkbox row 2: Console scanline | Theme effects if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) { ConsoleTab::s_scanline_enabled = sp_scanline_enabled; app->settings()->setScanlineEnabled(sp_scanline_enabled); @@ -876,9 +968,8 @@ void RenderSettingsPage(App* app) { } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects")); - // Acrylic blur slider (label above) float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, - contentW); + availWidth - pad * 2.0f); ImGui::TextUnformatted(TR("acrylic")); ImGui::SetNextItemWidth(ctrlW); { @@ -897,7 +988,6 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings()); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur")); - // Noise opacity slider (label above) ImGui::TextUnformatted(TR("noise")); ImGui::SetNextItemWidth(ctrlW); { @@ -914,7 +1004,6 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings()); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise")); - // UI Opacity slider (label above) ImGui::TextUnformatted(TR("ui_opacity")); ImGui::SetNextItemWidth(ctrlW); { @@ -928,7 +1017,6 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings()); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity")); - // Window Opacity slider (label above) ImGui::TextUnformatted(TR("window_opacity")); ImGui::SetNextItemWidth(ctrlW); { @@ -943,44 +1031,7 @@ void RenderSettingsPage(App* app) { ImGui::EndDisabled(); // low-spec ImGui::PopFont(); - } - } - - // ============================================================ - // Font Scale slider (always enabled, not affected by low-spec) - // ============================================================ - { - ImGui::PushFont(body2); - ImGui::Spacing(); - ImGui::TextUnformatted(TR("font_scale")); - 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]; - snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale); - ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt, - ImGuiSliderFlags_AlwaysClamp); - } - // 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, - 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); - } - if (ImGui::IsItemDeactivatedAfterEdit()) { - // On release: rebuild font atlas at final size - Layout::setUserFontScale(sp_font_scale); - saveSettingsPageState(app->settings()); - } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale")); - ImGui::PopFont(); + } // sp_effects_expanded } // Bottom padding @@ -1000,7 +1051,7 @@ void RenderSettingsPage(App* app) { ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== - // WALLET — card (Keys, Backup, Tools, Maintenance, Node/RPC) + // WALLET — card (privacy/daemon toggles + collapsible tools) // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet")); @@ -1013,56 +1064,6 @@ void RenderSettingsPage(App* app) { ImGui::Indent(pad); float contentW = availWidth - pad * 2; - float btnSpacing = Layout::spacingMd(); - float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f); - - // Calculate button width that fits available space - // 6 buttons total: on wide screens 3+3, on narrow screens 2+2+2 - int btnsPerRow = (contentW >= 600.0f) ? 3 : 2; - float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow; - // Clamp to reasonable size - float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f); - bw = std::max(minBtnW, bw); - - // Row 1 — Tools & Actions - { - if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button"))) - AddressBookDialog::show(); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book")); - ImGui::SameLine(0, btnSpacing); - if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button"))) - ValidateAddressDialog::show(); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate")); - if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } - if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button"))) - RequestPaymentDialog::show(); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment")); - if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); } - if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button"))) - ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining")); - ImGui::SameLine(0, btnSpacing); - if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button"))) - ShieldDialog::show(ShieldDialog::Mode::MergeToAddress); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge")); - if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } - if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) { - sp_confirm_clear_ztx = true; - } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx")); - } - - // Thin divider - ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - { - float divAlpha = S.drawElement("components.settings-page", "section-divider-alpha").opacity; - if (divAlpha <= 0.0f) divAlpha = 0.08f; - ImU32 baseDivCol = S.resolveColor("var(--status-divider)", IM_COL32(255, 255, 255, 20)); - ImU32 divCol = material::ScaleAlpha(baseDivCol, divAlpha / 0.08f); - ImVec2 p = ImGui::GetCursorScreenPos(); - dl->AddLine(ImVec2(p.x, p.y), ImVec2(p.x + contentW, p.y), divCol); - } - ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit { @@ -1111,47 +1112,94 @@ void RenderSettingsPage(App* app) { if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f); } - // Mine when idle — checkbox + delay combo + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // --- Collapsible: Tools & Actions... --- { - if (ImGui::Checkbox(TrId("mine_when_idle", "mine_idle").c_str(), &sp_mine_when_idle)) { - saveSettingsPageState(app->settings()); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", TR("tt_mine_idle")); - - if (sp_mine_when_idle) { - ImGui::SameLine(0, Layout::spacingMd()); - ImGui::AlignTextToFramePadding(); - ImGui::TextColored(ImVec4(1, 1, 1, 0.5f), "%s", TR("settings_idle_after")); - ImGui::SameLine(0, Layout::spacingSm()); - - struct DelayOption { int seconds; const char* label; }; - static const DelayOption delays[] = { - {30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"} - }; - const char* previewLabel = "2m"; - for (const auto& d : delays) { - if (d.seconds == sp_mine_idle_delay) { previewLabel = d.label; break; } + const char* arrow = sp_tools_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE; + ImGui::PushFont(body2); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f)); + { + ImVec2 hdrPos = ImGui::GetCursorScreenPos(); + if (ImGui::Button("##ToolsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) { + sp_tools_expanded = !sp_tools_expanded; } - - ImGui::SetNextItemWidth(schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f)); - if (ImGui::BeginCombo("##IdleDelay", previewLabel, ImGuiComboFlags_NoArrowButton)) { - for (const auto& d : delays) { - bool selected = (d.seconds == sp_mine_idle_delay); - if (ImGui::Selectable(d.label, selected)) { - sp_mine_idle_delay = d.seconds; - saveSettingsPageState(app->settings()); - } - if (selected) ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", TR("tt_idle_delay")); + float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f; + dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("tools_actions")); + ImFont* iconFont = Type().iconSmall(); + if (!iconFont) iconFont = body2; + float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x; + dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow); } + ImGui::PopStyleColor(3); + ImGui::PopFont(); } - // Bottom row — Keys & Data left-aligned, Setup Wizard right-aligned + if (sp_tools_expanded) { + float btnSpacing = Layout::spacingMd(); + int btnsPerRow = (contentW >= 600.0f) ? 3 : 2; + float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow; + float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f); + bw = std::max(minBtnW, bw); + + if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button"))) + AddressBookDialog::show(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book")); + ImGui::SameLine(0, btnSpacing); + if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button"))) + ValidateAddressDialog::show(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate")); + if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } + if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button"))) + RequestPaymentDialog::show(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment")); + if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); } + if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button"))) + ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining")); + ImGui::SameLine(0, btnSpacing); + if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button"))) + ShieldDialog::show(ShieldDialog::Mode::MergeToAddress); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge")); + if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } + if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) { + sp_confirm_clear_ztx = true; + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx")); + } + + ImGui::Dummy(ImVec2(0, bottomPad)); + ImGui::Unindent(pad); + + ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y); + dl->ChannelsSetCurrent(0); + DrawGlassPanel(dl, cardMin, cardMax, glassSpec); + dl->ChannelsMerge(); + + ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); + ImGui::Dummy(ImVec2(availWidth, 0)); + } + + ImGui::Dummy(ImVec2(0, gap)); + + // ==================================================================== + // BACKUP & DATA — card + // ==================================================================== + { + Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data")); + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + + ImVec2 cardMin = ImGui::GetCursorScreenPos(); + dl->ChannelsSplit(2); + dl->ChannelsSetCurrent(1); + ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad)); + ImGui::Indent(pad); + + float contentW = availWidth - pad * 2; + float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f); + { const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")}; const char* t1[] = { @@ -1165,13 +1213,12 @@ void RenderSettingsPage(App* app) { float sp = Layout::spacingSm(); ImFont* btnFont = S.resolveFont("button"); - // Measure natural widths float btnPadX = btnPad * 2; float naturalW = 0; for (int i = 0; i < 5; i++) naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX; float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX; - float totalW = naturalW + wizW + sp * 6; // 5 gaps between data btns + 1 gap before wizard + float totalW = naturalW + wizW + sp * 6; float scale = (totalW > contentW) ? contentW / totalW : 1.0f; if (scale < 1.0f) ImGui::SetWindowFontScale(scale); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 1457fde..1bbc1b6 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -174,6 +174,9 @@ void I18n::loadBuiltinEnglish() // Settings sections strings_["appearance"] = "APPEARANCE"; + strings_["theme_language"] = "THEME & LANGUAGE"; + strings_["advanced_effects"] = "Advanced Effects..."; + strings_["tools_actions"] = "Tools & Actions..."; strings_["wallet"] = "WALLET"; strings_["node_security"] = "NODE & SECURITY"; strings_["node"] = "NODE";