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
This commit is contained in:
dan_s
2026-03-11 01:38:40 -05:00
parent 96c27bb949
commit cf520fdf40
2 changed files with 200 additions and 150 deletions

View File

@@ -122,6 +122,8 @@ static bool sp_verbose_logging = false;
static std::set<std::string> 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);

View File

@@ -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";