feat(settings): shared design components + Wallet button hierarchy
Step 1: promote the polished chat-settings building blocks into reusable material components (src/ui/material/settings_controls.h) — SettingsSubheader (accent small-caps), SettingsRow (label left / control right), SegmentedControl (iOS track+pill), and a tiered ActionButton (Primary=accent fill, Secondary=glass, Tertiary=ghost, Destructive=error) with an optional leading Material icon, plus a ButtonFlow that wraps rows of buttons. Step 2: rebuild the Wallet BACKUP & DATA button wall on them. The 9–11 identical buttons that were font-scaled onto one row are now tier-ordered, icon-labelled, and wrap to new rows: the emphasized actions the user flagged — Import key, Seed phrase, Wallets…, Download Bootstrap — cluster on top as accent Primary buttons; common actions (viewing-key import, Backup, Migrate, Setup Wizard) are Secondary; exports are low-emphasis Tertiary. All click handlers/tooltips and the migrate "legacy wallet" glow are preserved. Removes the SetWindowFontScale legibility hack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
193
src/ui/material/settings_controls.h
Normal file
193
src/ui/material/settings_controls.h
Normal file
@@ -0,0 +1,193 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Settings design-system controls — the polished chat-settings look (accent subsection headers,
|
||||
// labeled rows with right-aligned controls, iOS-style segmented controls) promoted to reusable
|
||||
// components, plus a tiered ActionButton (Primary/Secondary/Tertiary/Destructive) with optional
|
||||
// leading Material icon and a ButtonFlow that wraps rows of buttons instead of shrinking them.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "draw_helpers.h" // colors, type, layout, icons, WithAlpha, ScaleAlpha, DrawButtonGlassOverlay
|
||||
#include "imgui.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
namespace material {
|
||||
|
||||
// Accent small-caps subsection header ("KEYS & BACKUP", "APPEARANCE"…) — the chat-settings section() look.
|
||||
inline void SettingsSubheader(const char* text) {
|
||||
const float dp = Layout::dpiScale();
|
||||
ImGui::Dummy(ImVec2(0.0f, 8.0f * dp));
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, WithAlpha(Primary(), 235));
|
||||
ImGui::TextUnformatted(text);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
ImGui::Dummy(ImVec2(0.0f, 2.0f * dp));
|
||||
}
|
||||
|
||||
// A labeled settings row: label left, control right-aligned in a fixed column. Construct once per card
|
||||
// section with the content width (0 = auto), then call .label(text) before drawing each control (leaves
|
||||
// the cursor at the control origin and sets the next item width to the control column).
|
||||
struct SettingsRow {
|
||||
float leftX, rowW, ctrlW, rowGap;
|
||||
explicit SettingsRow(float contentWidth, float controlWidth = 250.0f) {
|
||||
const float dp = Layout::dpiScale();
|
||||
leftX = ImGui::GetCursorPosX();
|
||||
rowW = (contentWidth > 0.0f) ? contentWidth : ImGui::GetContentRegionAvail().x;
|
||||
ctrlW = controlWidth * dp;
|
||||
rowGap = 5.0f * dp;
|
||||
}
|
||||
void label(const char* text) {
|
||||
ImGui::Dummy(ImVec2(0.0f, rowGap));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(text);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), leftX + std::max(0.0f, rowW - ctrlW)));
|
||||
ImGui::SetNextItemWidth(ctrlW);
|
||||
}
|
||||
};
|
||||
|
||||
// iOS-style segmented control (rounded track + inset pill on the selection). Draws its own labeled row
|
||||
// and returns the (possibly changed) index.
|
||||
inline int SegmentedControl(SettingsRow& row, const char* label, const char* const* items, int count, int value) {
|
||||
const float dp = Layout::dpiScale();
|
||||
row.label(label);
|
||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||
const float h = ImGui::GetFrameHeight();
|
||||
const float seg = row.ctrlW / static_cast<float>(count);
|
||||
const float round = 7.0f * dp;
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
dl->AddRectFilled(origin, ImVec2(origin.x + row.ctrlW, origin.y + h), WithAlpha(OnSurface(), 20), round);
|
||||
int result = value;
|
||||
ImGui::PushID(label);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ImGui::PushID(i);
|
||||
const ImVec2 mn(origin.x + i * seg, origin.y), mx(origin.x + (i + 1) * seg, origin.y + h);
|
||||
ImGui::SetCursorScreenPos(mn);
|
||||
if (ImGui::InvisibleButton("##s", ImVec2(seg, h))) result = i;
|
||||
const bool hov = ImGui::IsItemHovered();
|
||||
if (hov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
const bool sel = (value == i);
|
||||
if (sel) {
|
||||
const float in = 2.0f * dp;
|
||||
dl->AddRectFilled(ImVec2(mn.x + in, mn.y + in), ImVec2(mx.x - in, mx.y - in),
|
||||
WithAlpha(Primary(), 210), std::max(1.0f, round - in));
|
||||
} else if (hov) {
|
||||
dl->AddRectFilled(mn, mx, WithAlpha(OnSurface(), 26), round);
|
||||
}
|
||||
const ImVec2 ts = ImGui::CalcTextSize(items[i]);
|
||||
const float lpad = 4.0f * dp;
|
||||
const float tx = (ts.x <= seg - 2.0f * lpad) ? (seg - ts.x) * 0.5f : lpad;
|
||||
ImGui::PushClipRect(mn, mx, true);
|
||||
dl->AddText(ImVec2(mn.x + tx, mn.y + (h - ts.y) * 0.5f),
|
||||
sel ? IM_COL32(255, 255, 255, 236) : OnSurfaceMedium(), items[i]);
|
||||
ImGui::PopClipRect();
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SetCursorScreenPos(origin);
|
||||
ImGui::Dummy(ImVec2(row.ctrlW, h)); // reserve the control's rect for layout flow
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Tiered action buttons ───────────────────────────────────────────────────
|
||||
// Primary = filled accent (the one main action of a group)
|
||||
// Secondary = glass (default — common actions)
|
||||
// Tertiary = ghost / low-emphasis (rarely used)
|
||||
// Destructive = error-tinted outline (delete / reset)
|
||||
enum class ActionTier { Primary, Secondary, Tertiary, Destructive };
|
||||
|
||||
// The width an ActionButton will occupy (for ButtonFlow / manual layout).
|
||||
inline float ActionButtonWidth(const char* label, const char* icon, float minWidth = 0.0f) {
|
||||
ImFont* lf = Type().button();
|
||||
ImFont* icf = Type().iconSmall();
|
||||
const float dp = Layout::dpiScale();
|
||||
const float padX = 12.0f * dp, gap = 6.0f * dp;
|
||||
const float labelW = lf->CalcTextSizeA(lf->LegacySize, FLT_MAX, 0, label).x;
|
||||
const float iconW = (icon && icon[0] && icf) ? icf->CalcTextSizeA(icf->LegacySize, FLT_MAX, 0, icon).x : 0.0f;
|
||||
const float w = padX * 2.0f + iconW + (iconW > 0.0f ? gap : 0.0f) + labelW;
|
||||
return std::max(w, minWidth);
|
||||
}
|
||||
|
||||
// A tiered action button with an optional leading Material icon (ICON_MD_* or nullptr). Auto-sizes to
|
||||
// its content (>= minWidth). Respects BeginDisabled() (dims via the style alpha, not clickable).
|
||||
inline bool ActionButton(const char* id, const char* label, const char* icon, ActionTier tier, float minWidth = 0.0f) {
|
||||
ImFont* lf = Type().button();
|
||||
ImFont* icf = Type().iconSmall();
|
||||
const float dp = Layout::dpiScale();
|
||||
const float gap = 6.0f * dp;
|
||||
const float h = ImGui::GetFrameHeight();
|
||||
const bool hasIcon = icon && icon[0] && icf;
|
||||
const float labelW = lf->CalcTextSizeA(lf->LegacySize, FLT_MAX, 0, label).x;
|
||||
const float iconW = hasIcon ? icf->CalcTextSizeA(icf->LegacySize, FLT_MAX, 0, icon).x : 0.0f;
|
||||
const float w = ActionButtonWidth(label, icon, minWidth);
|
||||
|
||||
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
const bool pressed = ImGui::InvisibleButton(id, ImVec2(w, h));
|
||||
const bool hov = ImGui::IsItemHovered(), act = ImGui::IsItemActive();
|
||||
if (hov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const ImVec2 pMax(pos.x + w, pos.y + h);
|
||||
const float round = ImGui::GetStyle().FrameRounding;
|
||||
const float a = ImGui::GetStyle().Alpha; // BeginDisabled() lowers this
|
||||
|
||||
ImU32 bg = 0, border = 0, fg = OnSurface();
|
||||
bool glass = false;
|
||||
switch (tier) {
|
||||
case ActionTier::Primary:
|
||||
bg = WithAlpha(Primary(), act ? 255 : (hov ? 245 : 220));
|
||||
fg = IM_COL32(255, 255, 255, 240);
|
||||
break;
|
||||
case ActionTier::Secondary:
|
||||
bg = WithAlpha(OnSurface(), hov ? 26 : 16);
|
||||
border = WithAlpha(OnSurface(), 40);
|
||||
glass = true;
|
||||
fg = OnSurface();
|
||||
break;
|
||||
case ActionTier::Tertiary:
|
||||
bg = hov ? WithAlpha(OnSurface(), 18) : 0;
|
||||
fg = OnSurfaceMedium();
|
||||
break;
|
||||
case ActionTier::Destructive:
|
||||
bg = hov ? WithAlpha(Error(), 32) : WithAlpha(Error(), 12);
|
||||
border = WithAlpha(Error(), 90);
|
||||
fg = Error();
|
||||
break;
|
||||
}
|
||||
if (bg) dl->AddRectFilled(pos, pMax, ScaleAlpha(bg, a), round);
|
||||
if (border) dl->AddRect(pos, pMax, ScaleAlpha(border, a), round, 0, 1.0f);
|
||||
if (glass) DrawButtonGlassOverlay(dl, pos, pMax, round, act, hov);
|
||||
fg = ScaleAlpha(fg, a);
|
||||
|
||||
const float contentW = iconW + (iconW > 0.0f ? gap : 0.0f) + labelW;
|
||||
float cx = pos.x + (w - contentW) * 0.5f;
|
||||
if (hasIcon) {
|
||||
dl->AddText(icf, icf->LegacySize, ImVec2(cx, pos.y + (h - icf->LegacySize) * 0.5f), fg, icon);
|
||||
cx += iconW + gap;
|
||||
}
|
||||
dl->AddText(lf, lf->LegacySize, ImVec2(cx, pos.y + (h - lf->LegacySize) * 0.5f), fg, label);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
// Places ActionButtons left→right, wrapping to a new row when the next one won't fit (instead of the
|
||||
// old font-scale-to-fit). Call next(width) before each ActionButton.
|
||||
struct ButtonFlow {
|
||||
float availW, gap; float x = 0.0f; bool firstOnRow = true;
|
||||
explicit ButtonFlow(float availWidth, float gapPx = 8.0f) : availW(availWidth) {
|
||||
gap = gapPx * Layout::dpiScale();
|
||||
}
|
||||
void next(float w) {
|
||||
if (firstOnRow) { firstOnRow = false; x = w; return; }
|
||||
if (x + gap + w <= availW) { ImGui::SameLine(0, gap); x += gap + w; }
|
||||
else { x = w; } // natural newline wraps to the next row
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace material
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "../effects/low_spec.h"
|
||||
#include "../effects/scroll_fade_shader.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/settings_controls.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../windows/validate_address_dialog.h"
|
||||
@@ -1390,119 +1391,72 @@ void RenderSettingsPage(App* app) {
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_clear_ztx"));
|
||||
}
|
||||
|
||||
// --- Backup & Data (merged in from its former standalone card) ---
|
||||
// --- Backup & Data ---
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
{
|
||||
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[] = { TR("tt_import_key"), TR("tt_export_key"), TR("tt_export_all"), TR("tt_backup"), TR("tt_export_csv") };
|
||||
const bool showFullNodeLifecycleActions = app->supportsFullNodeLifecycleActions();
|
||||
const char* wizLabel = TR("setup_wizard");
|
||||
const char* bsLabel = TR("download_bootstrap");
|
||||
float sp = Layout::spacingSm();
|
||||
ImFont* btnFont = S.resolveFont("button");
|
||||
using AT = material::ActionTier;
|
||||
const bool fullNode = app->supportsFullNodeLifecycleActions();
|
||||
// Tier-ordered, icon-labelled buttons that WRAP to new rows (no more font-scale-to-fit).
|
||||
// Emphasized actions (accent) cluster on top, then common actions, then low-emphasis exports.
|
||||
auto btn = [&](material::ButtonFlow& fl, const char* id, const char* label, const char* icon,
|
||||
AT tier, const char* tip) -> bool {
|
||||
fl.next(material::ActionButtonWidth(label, icon));
|
||||
const bool p = material::ActionButton(id, label, icon, tier);
|
||||
if (ImGui::IsItemHovered() && tip && tip[0]) material::Tooltip("%s", tip);
|
||||
return p;
|
||||
};
|
||||
|
||||
float btnPadX = btnPad * 2;
|
||||
float naturalW = 0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
||||
float impViewW = ImGui::CalcTextSize(TR("settings_import_viewkey")).x + btnPadX;
|
||||
naturalW += impViewW; // the extra "Import viewing key" button (rendered after Import key)
|
||||
float wizW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(wizLabel).x + btnPadX : 0.0f;
|
||||
float bsW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(bsLabel).x + btnPadX : 0.0f;
|
||||
// Full-node-only "Seed phrase" + "Migrate to seed" buttons trail the Backup button.
|
||||
float seedW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(TR("seed_backup_button")).x + btnPadX : 0.0f;
|
||||
float migrateW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(TR("seed_migrate_button")).x + btnPadX : 0.0f;
|
||||
float totalW = naturalW + sp * 6; // 6 base buttons (incl. Import viewing key)
|
||||
if (showFullNodeLifecycleActions) totalW += wizW + bsW + seedW + migrateW + sp * 4;
|
||||
|
||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||
float scaledSp = sp * scale;
|
||||
|
||||
if (TactileButton(r1[0], ImVec2(0, 0), btnFont))
|
||||
// Emphasized (Primary): import key + (full-node) seed / wallets / bootstrap.
|
||||
material::ButtonFlow fPrim(contentW);
|
||||
if (btn(fPrim, "##imp_key", TR("settings_import_key"), ICON_MD_KEY, AT::Primary, TR("tt_import_key")))
|
||||
app->showImportKeyDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[0]);
|
||||
// Watch-only shielded viewing-key import (separate button — different key type + scan-height).
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(TR("settings_import_viewkey"), ImVec2(0, 0), btnFont))
|
||||
app->showImportViewingKeyDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_import_viewkey"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(r1[1], ImVec2(0, 0), btnFont))
|
||||
app->showExportKeyDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[1]);
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(r1[2], ImVec2(0, 0), btnFont))
|
||||
ExportAllKeysDialog::show();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[2]);
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(r1[3], ImVec2(0, 0), btnFont))
|
||||
app->showBackupDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[3]);
|
||||
if (showFullNodeLifecycleActions) {
|
||||
// Back up the wallet's BIP39 seed phrase (full-node mnemonic wallets).
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(TR("seed_backup_button"), ImVec2(0, 0), btnFont))
|
||||
if (fullNode) {
|
||||
if (btn(fPrim, "##seed", TR("seed_backup_button"), ICON_MD_VPN_KEY, AT::Primary, TR("tt_seed_backup")))
|
||||
app->showSeedBackupDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_backup"));
|
||||
// Migrate to a new seed-phrase wallet (create in isolation, then sweep funds). A
|
||||
// legacy (pre-seed-phrase) wallet glows the button to nudge the user toward migrating.
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (btn(fPrim, "##wallets", TR("wallets_button"), ICON_MD_ACCOUNT_BALANCE_WALLET, AT::Primary, TR("tt_wallets_button")))
|
||||
ui::WalletsDialog::show(app);
|
||||
if (btn(fPrim, "##bootstrap", TR("download_bootstrap"), ICON_MD_CLOUD_DOWNLOAD, AT::Primary, TR("tt_download_bootstrap")))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
}
|
||||
|
||||
// Common (Secondary): viewing-key import, backup, (full-node) migrate + setup wizard.
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
material::ButtonFlow fSec(contentW);
|
||||
if (btn(fSec, "##imp_vk", TR("settings_import_viewkey"), ICON_MD_VISIBILITY, AT::Secondary, TR("tt_import_viewkey")))
|
||||
app->showImportViewingKeyDialog();
|
||||
if (btn(fSec, "##backup", TR("settings_backup"), ICON_MD_BACKUP, AT::Secondary, TR("tt_backup")))
|
||||
app->showBackupDialog();
|
||||
if (fullNode) {
|
||||
const bool migrateGlow = app->isPreSeedWallet();
|
||||
if (TactileButton(TR("seed_migrate_button"), ImVec2(0, 0), btnFont))
|
||||
if (btn(fSec, "##migrate", TR("seed_migrate_button"), ICON_MD_SWAP_HORIZ, AT::Secondary, TR("tt_seed_migrate")))
|
||||
app->showSeedMigrationDialog();
|
||||
if (migrateGlow) {
|
||||
if (migrateGlow) { // pulsing accent halo nudging a legacy wallet to migrate
|
||||
const ImVec2 gmn = ImGui::GetItemRectMin(), gmx = ImGui::GetItemRectMax();
|
||||
const float gdp = Layout::dpiScale();
|
||||
const float pulse = 0.5f + 0.5f * std::sin((float)ImGui::GetTime() * 3.2f); // 0..1
|
||||
const float pulse = 0.5f + 0.5f * std::sin((float)ImGui::GetTime() * 3.2f);
|
||||
ImDrawList* gdl = ImGui::GetWindowDrawList();
|
||||
for (int g = 3; g >= 1; --g) { // soft, pulsing outward halo in the accent color
|
||||
for (int g = 3; g >= 1; --g) {
|
||||
const float e = (float)g * 2.2f * gdp;
|
||||
const int a = (int)((70.0f + pulse * 95.0f) / (float)g);
|
||||
gdl->AddRect(ImVec2(gmn.x - e, gmn.y - e), ImVec2(gmx.x + e, gmx.y + e),
|
||||
material::WithAlpha(material::Primary(), a), 6.0f * gdp + e, 0, 1.6f * gdp);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_migrate"));
|
||||
// Multi-wallet: list wallet files + switch the active one.
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(TR("wallets_button"), ImVec2(0, 0), btnFont))
|
||||
ui::WalletsDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wallets_button"));
|
||||
}
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(r1[4], ImVec2(0, 0), btnFont))
|
||||
ExportTransactionsDialog::show();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[4]);
|
||||
|
||||
if (showFullNodeLifecycleActions) {
|
||||
// Right-align Setup Wizard + Download Bootstrap.
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
if (btn(fSec, "##wizard", TR("setup_wizard"), ICON_MD_AUTO_FIX_HIGH, AT::Secondary, TR("tt_wizard")))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_wizard"));
|
||||
}
|
||||
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
||||
// Low-emphasis (Tertiary): exports.
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
material::ButtonFlow fTer(contentW);
|
||||
if (btn(fTer, "##exp_key", TR("settings_export_key"), ICON_MD_LOGOUT, AT::Tertiary, TR("tt_export_key")))
|
||||
app->showExportKeyDialog();
|
||||
if (btn(fTer, "##exp_all", TR("settings_export_all"), ICON_MD_ARCHIVE, AT::Tertiary, TR("tt_export_all")))
|
||||
ExportAllKeysDialog::show();
|
||||
if (btn(fTer, "##exp_csv", TR("settings_export_csv"), ICON_MD_DESCRIPTION, AT::Tertiary, TR("tt_export_csv")))
|
||||
ExportTransactionsDialog::show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user