refactor(ui): consolidate dialog footers + shared empty-state, unify buttons/rounding
Addresses the cross-screen inconsistencies from the layout audit by routing screens onto the design system's own (previously under-used) shared helpers: - Dialog footers: migrate ~9 overlay dialogs off hand-rolled placement onto the shared helpers — DialogActionFooter (primary+Close), DialogConfirmFooter, or BeginOverlayDialogFooter for custom/multi-button rows — so footers share one centered treatment. All footer/action buttons now use TactileButton (glass press) instead of the bare StyledButton some dialogs used. - Empty states: add a shared material::DrawEmptyState(icon, title, hint) (centered icon + title + wrapped hint) and adopt it in Peers, Transactions, and Market-portfolio, which previously showed a bare left-aligned caption. - Security dialogs: add the missing Cancel to Change Passphrase and Set PIN so the whole security family shares a two-button footer (Cancel dismisses without applying). - Transactions pager: shared TactileButton helpers (matching Explorer). - Frosted-pane rounding: Contacts/Chat use Layout::glassRounding() instead of hardcoded 12/10/8px literals, matching Peers. - "Set Label..." title loses its stray trailing ellipsis. Preserves every button's label and action; the transfer footer's order becomes [Confirm][Cancel] to match the shared helper's primary-first convention. Verified at 1280 across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1312,7 +1312,7 @@
|
|||||||
"sent_filter": "Gesendet",
|
"sent_filter": "Gesendet",
|
||||||
"sent_type": "Gesendet",
|
"sent_type": "Gesendet",
|
||||||
"sent_upper": "GESENDET",
|
"sent_upper": "GESENDET",
|
||||||
"set_label": "Label setzen...",
|
"set_label": "Label setzen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"settings_about_text": "Eine geschirmte Kryptowährungs-Wallet für DragonX (DRGX), erstellt mit Dear ImGui für ein leichtes, portables Erlebnis.",
|
"settings_about_text": "Eine geschirmte Kryptowährungs-Wallet für DragonX (DRGX), erstellt mit Dear ImGui für ein leichtes, portables Erlebnis.",
|
||||||
"settings_acrylic_level": "Acrylstufe:",
|
"settings_acrylic_level": "Acrylstufe:",
|
||||||
|
|||||||
@@ -1312,7 +1312,7 @@
|
|||||||
"sent_filter": "Enviado",
|
"sent_filter": "Enviado",
|
||||||
"sent_type": "Enviado",
|
"sent_type": "Enviado",
|
||||||
"sent_upper": "ENVIADO",
|
"sent_upper": "ENVIADO",
|
||||||
"set_label": "Establecer Etiqueta...",
|
"set_label": "Establecer Etiqueta",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"settings_about_text": "Una billetera de criptomonedas blindada para DragonX (DRGX), creada con Dear ImGui para una experiencia ligera y portátil.",
|
"settings_about_text": "Una billetera de criptomonedas blindada para DragonX (DRGX), creada con Dear ImGui para una experiencia ligera y portátil.",
|
||||||
"settings_acrylic_level": "Nivel de acrílico:",
|
"settings_acrylic_level": "Nivel de acrílico:",
|
||||||
|
|||||||
@@ -1312,7 +1312,7 @@
|
|||||||
"sent_filter": "Envoyé",
|
"sent_filter": "Envoyé",
|
||||||
"sent_type": "Envoyé",
|
"sent_type": "Envoyé",
|
||||||
"sent_upper": "ENVOYÉ",
|
"sent_upper": "ENVOYÉ",
|
||||||
"set_label": "Définir le libellé...",
|
"set_label": "Définir le libellé",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"settings_about_text": "Un portefeuille de cryptomonnaie blindé pour DragonX (DRGX), construit avec Dear ImGui pour une expérience légère et portable.",
|
"settings_about_text": "Un portefeuille de cryptomonnaie blindé pour DragonX (DRGX), construit avec Dear ImGui pour une expérience légère et portable.",
|
||||||
"settings_acrylic_level": "Niveau acrylique :",
|
"settings_acrylic_level": "Niveau acrylique :",
|
||||||
|
|||||||
@@ -1309,7 +1309,7 @@
|
|||||||
"sent_filter": "送信済み",
|
"sent_filter": "送信済み",
|
||||||
"sent_type": "送信済み",
|
"sent_type": "送信済み",
|
||||||
"sent_upper": "送信済み",
|
"sent_upper": "送信済み",
|
||||||
"set_label": "ラベルを設定...",
|
"set_label": "ラベルを設定",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
"settings_about_text": "DragonX (DRGX) 用のシールド暗号通貨ウォレット。Dear ImGui で構築された軽量でポータブルな体験。",
|
"settings_about_text": "DragonX (DRGX) 用のシールド暗号通貨ウォレット。Dear ImGui で構築された軽量でポータブルな体験。",
|
||||||
"settings_acrylic_level": "アクリルレベル:",
|
"settings_acrylic_level": "アクリルレベル:",
|
||||||
|
|||||||
@@ -1311,7 +1311,7 @@
|
|||||||
"sent_filter": "전송됨",
|
"sent_filter": "전송됨",
|
||||||
"sent_type": "전송됨",
|
"sent_type": "전송됨",
|
||||||
"sent_upper": "전송됨",
|
"sent_upper": "전송됨",
|
||||||
"set_label": "라벨 설정...",
|
"set_label": "라벨 설정",
|
||||||
"settings": "설정",
|
"settings": "설정",
|
||||||
"settings_about_text": "DragonX (DRGX)용 차폐 암호화폐 지갑으로, Dear ImGui로 제작되어 가볍고 휴대 가능합니다.",
|
"settings_about_text": "DragonX (DRGX)용 차폐 암호화폐 지갑으로, Dear ImGui로 제작되어 가볍고 휴대 가능합니다.",
|
||||||
"settings_acrylic_level": "아크릴 레벨:",
|
"settings_acrylic_level": "아크릴 레벨:",
|
||||||
|
|||||||
@@ -1312,7 +1312,7 @@
|
|||||||
"sent_filter": "Enviado",
|
"sent_filter": "Enviado",
|
||||||
"sent_type": "Enviado",
|
"sent_type": "Enviado",
|
||||||
"sent_upper": "ENVIADO",
|
"sent_upper": "ENVIADO",
|
||||||
"set_label": "Definir Rótulo...",
|
"set_label": "Definir Rótulo",
|
||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"settings_about_text": "Uma carteira de criptomoeda blindada para DragonX (DRGX), criada com Dear ImGui para uma experiência leve e portátil.",
|
"settings_about_text": "Uma carteira de criptomoeda blindada para DragonX (DRGX), criada com Dear ImGui para uma experiência leve e portátil.",
|
||||||
"settings_acrylic_level": "Nível acrílico:",
|
"settings_acrylic_level": "Nível acrílico:",
|
||||||
|
|||||||
@@ -1312,7 +1312,7 @@
|
|||||||
"sent_filter": "Отправлено",
|
"sent_filter": "Отправлено",
|
||||||
"sent_type": "Отправлено",
|
"sent_type": "Отправлено",
|
||||||
"sent_upper": "ОТПРАВЛЕНО",
|
"sent_upper": "ОТПРАВЛЕНО",
|
||||||
"set_label": "Установить метку...",
|
"set_label": "Установить метку",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"settings_about_text": "Защищённый криптовалютный кошелёк для DragonX (DRGX), созданный на Dear ImGui для лёгкого и портативного использования.",
|
"settings_about_text": "Защищённый криптовалютный кошелёк для DragonX (DRGX), созданный на Dear ImGui для лёгкого и портативного использования.",
|
||||||
"settings_acrylic_level": "Уровень акрила:",
|
"settings_acrylic_level": "Уровень акрила:",
|
||||||
|
|||||||
@@ -1310,7 +1310,7 @@
|
|||||||
"sent_filter": "已发送",
|
"sent_filter": "已发送",
|
||||||
"sent_type": "已发送",
|
"sent_type": "已发送",
|
||||||
"sent_upper": "已发送",
|
"sent_upper": "已发送",
|
||||||
"set_label": "设置标签...",
|
"set_label": "设置标签",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"settings_about_text": "DragonX (DRGX) 屏蔽加密货币钱包,使用 Dear ImGui 构建,提供轻量、便携的体验。",
|
"settings_about_text": "DragonX (DRGX) 屏蔽加密货币钱包,使用 Dear ImGui 构建,提供轻量、便携的体验。",
|
||||||
"settings_acrylic_level": "亚克力级别:",
|
"settings_acrylic_level": "亚克力级别:",
|
||||||
|
|||||||
@@ -1476,12 +1476,22 @@ void App::renderEncryptWalletDialog() {
|
|||||||
bool valid = strlen(change_old_pass_buf_) > 0 &&
|
bool valid = strlen(change_old_pass_buf_) > 0 &&
|
||||||
strlen(change_new_pass_buf_) >= 8 &&
|
strlen(change_new_pass_buf_) >= 8 &&
|
||||||
strcmp(change_new_pass_buf_, change_confirm_buf_) == 0;
|
strcmp(change_new_pass_buf_, change_confirm_buf_) == 0;
|
||||||
|
|
||||||
|
// Two-button footer (primary + Cancel) to match the encrypt/decrypt siblings.
|
||||||
|
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||||
ImGui::BeginDisabled(!valid || encrypt_in_progress_);
|
ImGui::BeginDisabled(!valid || encrypt_in_progress_);
|
||||||
if (ui::material::TactileButton(TR("change_pass_title"), ImVec2(-1, 40))) {
|
if (ui::material::TactileButton(TR("change_pass_title"), ImVec2(btnW, 40))) {
|
||||||
changePassphrase(std::string(change_old_pass_buf_),
|
changePassphrase(std::string(change_old_pass_buf_),
|
||||||
std::string(change_new_pass_buf_));
|
std::string(change_new_pass_buf_));
|
||||||
}
|
}
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
// Cancel does what Esc/close does — dismiss without applying. Buffers are wiped by
|
||||||
|
// the !show_change_passphrase_ cleanup block below.
|
||||||
|
if (ui::material::TactileButton(TR("cancel"), ImVec2(btnW, 40))) {
|
||||||
|
show_change_passphrase_ = false;
|
||||||
|
}
|
||||||
EndOverlayDialog();
|
EndOverlayDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1945,8 +1955,10 @@ void App::renderPinDialogs() {
|
|||||||
util::SecureVault::isValidPin(pinStr) &&
|
util::SecureVault::isValidPin(pinStr) &&
|
||||||
strcmp(pin_buf_, pin_confirm_buf_) == 0;
|
strcmp(pin_buf_, pin_confirm_buf_) == 0;
|
||||||
|
|
||||||
|
// Two-button footer (primary + Cancel) to match the encrypt/decrypt siblings.
|
||||||
|
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||||
if (ui::material::TactileButton(TR("settings_set_pin"), ImVec2(-1, 40))) {
|
if (ui::material::TactileButton(TR("settings_set_pin"), ImVec2(btnW, 40))) {
|
||||||
pin_in_progress_ = true;
|
pin_in_progress_ = true;
|
||||||
pin_status_ = "Verifying passphrase...";
|
pin_status_ = "Verifying passphrase...";
|
||||||
|
|
||||||
@@ -2005,6 +2017,13 @@ void App::renderPinDialogs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
// Cancel does what Esc/close does — dismiss without applying. Buffers are wiped by
|
||||||
|
// the !show_pin_setup_ cleanup block below.
|
||||||
|
if (ui::material::TactileButton(TR("cancel"), ImVec2(btnW, 40))) {
|
||||||
|
show_pin_setup_ = false;
|
||||||
|
}
|
||||||
EndOverlayDialog();
|
EndOverlayDialog();
|
||||||
}
|
}
|
||||||
// Wipe the passphrase/PIN buffers if the dialog was dismissed (X / Esc /
|
// Wipe the passphrase/PIN buffers if the dialog was dismissed (X / Esc /
|
||||||
|
|||||||
@@ -79,6 +79,63 @@ inline const char* LoadingDots() {
|
|||||||
return kDots[n];
|
return kDots[n];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Centered empty state ─────────────────────────────────────────────────
|
||||||
|
// A big muted icon + title + optional wrapped hint, centered on BOTH axes within
|
||||||
|
// GetContentRegionAvail(). Mirrors chat_tab's centeredEmptyState so list-empty states
|
||||||
|
// read the same across tabs. Call at the start of the region you want it centered in
|
||||||
|
// (e.g. right after a BeginChild / a leading Dummy). Font metrics use the live font
|
||||||
|
// scale (LegacySize * FontScaleMain) and PushFont draws at that same scale, so this is
|
||||||
|
// crisp at HiDPI / font_scale 1.5 without any manual dpiScale multiply on the metrics.
|
||||||
|
inline void DrawEmptyState(const char* iconGlyph, const char* title, const char* hint = nullptr)
|
||||||
|
{
|
||||||
|
auto scaled = [](ImFont* f) { return f->LegacySize * ImGui::GetStyle().FontScaleMain; };
|
||||||
|
const ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||||
|
const ImVec2 origin = ImGui::GetCursorPos();
|
||||||
|
ImFont* iconF = Type().iconXL();
|
||||||
|
ImFont* titleF = Type().subtitle1();
|
||||||
|
ImFont* hintF = Type().body2();
|
||||||
|
const float dp = Layout::dpiScale();
|
||||||
|
const float gap = 8.0f * dp;
|
||||||
|
const float wrap = std::min(avail.x - 40.0f * dp, 360.0f * dp);
|
||||||
|
const float iconSz = iconF ? scaled(iconF) : 40.0f;
|
||||||
|
const float iconH = (iconF && iconGlyph) ? iconF->CalcTextSizeA(iconSz, FLT_MAX, 0.0f, iconGlyph).y : 0.0f;
|
||||||
|
const float titleH = titleF->CalcTextSizeA(scaled(titleF), FLT_MAX, 0.0f, title).y;
|
||||||
|
const float hintH = hint ? hintF->CalcTextSizeA(scaled(hintF), wrap, wrap, hint).y : 0.0f;
|
||||||
|
const float totalH = iconH + (iconH > 0.0f ? gap : 0.0f) + titleH + (hint ? gap + hintH : 0.0f);
|
||||||
|
float y = origin.y + std::max(0.0f, (avail.y - totalH) * 0.5f);
|
||||||
|
|
||||||
|
if (iconF && iconGlyph && iconGlyph[0]) {
|
||||||
|
const float iw = iconF->CalcTextSizeA(iconSz, FLT_MAX, 0.0f, iconGlyph).x;
|
||||||
|
ImGui::SetCursorPos(ImVec2(origin.x + (avail.x - iw) * 0.5f, y));
|
||||||
|
ImGui::PushFont(iconF);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, WithAlpha(OnSurface(), 70));
|
||||||
|
ImGui::TextUnformatted(iconGlyph);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopFont();
|
||||||
|
y += iconH + gap;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const float tw = titleF->CalcTextSizeA(scaled(titleF), FLT_MAX, 0.0f, title).x;
|
||||||
|
ImGui::SetCursorPos(ImVec2(origin.x + (avail.x - tw) * 0.5f, y));
|
||||||
|
ImGui::PushFont(titleF);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceMedium());
|
||||||
|
ImGui::TextUnformatted(title);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopFont();
|
||||||
|
y += titleH + gap;
|
||||||
|
}
|
||||||
|
if (hint) {
|
||||||
|
ImGui::SetCursorPos(ImVec2(origin.x + (avail.x - wrap) * 0.5f, y));
|
||||||
|
ImGui::PushFont(hintF);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, WithAlpha(OnSurface(), 120));
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap);
|
||||||
|
ImGui::TextUnformatted(hint);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Text Drop Shadow
|
// Text Drop Shadow
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -125,29 +125,26 @@ void RenderAboutDialog(App* app, bool* p_open)
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::TextWrapped("%s", TR("about_license_text"));
|
ImGui::TextWrapped("%s", TR("about_license_text"));
|
||||||
|
|
||||||
ImGui::Spacing();
|
// Links — 3-button action row, centered via the shared footer helper (draws its own
|
||||||
ImGui::Separator();
|
// Spacing/Separator/Spacing above the row, replacing the hand-rolled divider block).
|
||||||
ImGui::Spacing();
|
const float linksTotalW = linkW * 3.0f + ImGui::GetStyle().ItemSpacing.x * 2.0f;
|
||||||
|
material::BeginOverlayDialogFooter(linksTotalW);
|
||||||
// Links
|
if (material::TactileButton(TR("about_website"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||||
if (material::StyledButton(TR("about_website"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
|
||||||
util::Platform::openUrl("https://dragonx.is");
|
util::Platform::openUrl("https://dragonx.is");
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (material::StyledButton(TR("about_github"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
if (material::TactileButton(TR("about_github"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||||
util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon");
|
util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon");
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (material::StyledButton(TR("about_block_explorer"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
if (material::TactileButton(TR("about_block_explorer"), ImVec2(linkW, 0), S.resolveFont(linkBtn.font))) {
|
||||||
util::Platform::openUrl("https://explorer.dragonx.is");
|
util::Platform::openUrl("https://explorer.dragonx.is");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
// Close button — lone dismiss action, centered via the shared footer helper (no extra
|
||||||
|
// divider above it, so it sits directly under the links row).
|
||||||
// Close button
|
material::BeginOverlayDialogFooter(closeW, false);
|
||||||
float button_width = closeW;
|
if (material::TactileButton(TR("close"), ImVec2(closeW, 0), S.resolveFont(closeBtn.font))) {
|
||||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) * 0.5f);
|
|
||||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
|
||||||
*p_open = false;
|
*p_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,16 +180,14 @@ public:
|
|||||||
const char* sendingLabel = TR("sending");
|
const char* sendingLabel = TR("sending");
|
||||||
ImFont* buttonFont = Type().button();
|
ImFont* buttonFont = Type().button();
|
||||||
float buttonFontSize = ScaledFontSize(buttonFont);
|
float buttonFontSize = ScaledFontSize(buttonFont);
|
||||||
float minBtnW = 120.0f * dp;
|
|
||||||
float confirmMinW = 160.0f * dp;
|
float confirmMinW = 160.0f * dp;
|
||||||
float buttonPadW = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f * dp;
|
float buttonPadW = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f * dp;
|
||||||
float cancelW = std::max(minBtnW,
|
// Both footer buttons share one width (equal-width primary/Close pair), sized to fit the
|
||||||
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, cancelLabel).x + buttonPadW);
|
// widest label — the "Sending…" swap label included — so nothing clips.
|
||||||
float confirmTextW = std::max(
|
float confirmTextW = std::max(
|
||||||
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, confirmLabel).x,
|
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, confirmLabel).x,
|
||||||
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, sendingLabel).x);
|
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, sendingLabel).x);
|
||||||
float confirmW = std::max(confirmMinW, confirmTextW + buttonPadW);
|
float btnW = std::max(confirmMinW, confirmTextW + buttonPadW);
|
||||||
float totalW = cancelW + confirmW + Layout::spacingMd();
|
|
||||||
float footerH = ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.y * 3.0f; // footer divider removed
|
float footerH = ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.y * 3.0f; // footer divider removed
|
||||||
ImGuiViewport* vp = ImGui::GetMainViewport();
|
ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||||
float cardBottomY = vp->Pos.y + vp->Size.y * 0.85f;
|
float cardBottomY = vp->Pos.y + vp->Size.y * 0.85f;
|
||||||
@@ -201,19 +199,18 @@ public:
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
// Standardized primary + Close footer (centered, no divider). The primary is the
|
||||||
|
// Confirm/"Sending…" action; the Close button dismisses the dialog.
|
||||||
|
bool outConfirm = false;
|
||||||
|
bool outClose = false;
|
||||||
|
DialogActionFooter(s_sending ? sendingLabel : confirmLabel,
|
||||||
|
amountValid && !s_sending,
|
||||||
|
cancelLabel, outConfirm, outClose, btnW);
|
||||||
|
|
||||||
float rowStartX = ImGui::GetCursorPosX();
|
if (outClose) {
|
||||||
float contentW = ImGui::GetContentRegionAvail().x;
|
|
||||||
ImGui::SetCursorPosX(rowStartX + std::max(0.0f, (contentW - totalW) * 0.5f));
|
|
||||||
|
|
||||||
if (TactileButton(cancelLabel, ImVec2(cancelW, 0), buttonFont)) {
|
|
||||||
s_open = false;
|
s_open = false;
|
||||||
}
|
}
|
||||||
ImGui::SameLine(0, Layout::spacingMd());
|
if (outConfirm) {
|
||||||
|
|
||||||
ImGui::BeginDisabled(!amountValid || s_sending);
|
|
||||||
if (TactileButton(s_sending ? sendingLabel : confirmLabel, ImVec2(confirmW, 0), buttonFont)) {
|
|
||||||
s_sending = true;
|
s_sending = true;
|
||||||
s_app->sendTransaction(s_info.fromAddr, s_info.toAddr,
|
s_app->sendTransaction(s_info.fromAddr, s_info.toAddr,
|
||||||
amount, s_fee, "",
|
amount, s_fee, "",
|
||||||
@@ -231,7 +228,6 @@ public:
|
|||||||
// state, and when the async callback sets s_resultMsg the in-dialog result screen shows
|
// state, and when the async callback sets s_resultMsg the in-dialog result screen shows
|
||||||
// (with its own Close button). Previously closing here made that result screen dead code.
|
// (with its own Close button). Previously closing here made that result screen dead code.
|
||||||
}
|
}
|
||||||
ImGui::EndDisabled();
|
|
||||||
|
|
||||||
EndOverlayDialog();
|
EndOverlayDialog();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ void BlockInfoDialog::render(App* app)
|
|||||||
ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (material::StyledButton(TR("block_get_info"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
if (material::TactileButton(TR("block_get_info"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||||
if (rpc && rpc->isConnected() && app->worker()) {
|
if (rpc && rpc->isConnected() && app->worker()) {
|
||||||
s_loading = true;
|
s_loading = true;
|
||||||
s_error.clear();
|
s_error.clear();
|
||||||
@@ -303,7 +303,7 @@ void BlockInfoDialog::render(App* app)
|
|||||||
// Navigation buttons
|
// Navigation buttons
|
||||||
if (s_has_data) {
|
if (s_has_data) {
|
||||||
if (s_height > 1) {
|
if (s_height > 1) {
|
||||||
if (material::StyledButton(TR("block_nav_prev"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
if (material::TactileButton(TR("block_nav_prev"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||||
s_height--;
|
s_height--;
|
||||||
s_has_data = false;
|
s_has_data = false;
|
||||||
s_error.clear();
|
s_error.clear();
|
||||||
@@ -315,7 +315,7 @@ void BlockInfoDialog::render(App* app)
|
|||||||
// nextblockhash, so this stays hidden there).
|
// nextblockhash, so this stays hidden there).
|
||||||
if (!s_next_hash.empty() &&
|
if (!s_next_hash.empty() &&
|
||||||
(state.sync.blocks <= 0 || s_height < state.sync.blocks)) {
|
(state.sync.blocks <= 0 || s_height < state.sync.blocks)) {
|
||||||
if (material::StyledButton(TR("block_nav_next"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
if (material::TactileButton(TR("block_nav_next"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||||
s_height++;
|
s_height++;
|
||||||
s_has_data = false;
|
s_has_data = false;
|
||||||
s_error.clear();
|
s_error.clear();
|
||||||
@@ -323,9 +323,10 @@ void BlockInfoDialog::render(App* app)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close button at bottom
|
// Close button at bottom — centered via the shared footer helper (no separator, matching
|
||||||
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 40);
|
// the prior hand-rolled placement).
|
||||||
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
material::BeginOverlayDialogFooter(closeBtn.width, /*drawSeparator=*/false);
|
||||||
|
if (material::TactileButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||||
s_open = false;
|
s_open = false;
|
||||||
}
|
}
|
||||||
material::EndOverlayDialog();
|
material::EndOverlayDialog();
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ void RenderChatSettingsPreview(App* app, float width) {
|
|||||||
totalH += padIn;
|
totalH += padIn;
|
||||||
|
|
||||||
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
const ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||||
material::GlassPanelSpec g; g.rounding = 10.0f * dp; g.fillAlpha = 16; g.borderAlpha = 36;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 16; g.borderAlpha = 36;
|
||||||
material::DrawGlassPanel(dl, origin, ImVec2(origin.x + width, origin.y + totalH), g);
|
material::DrawGlassPanel(dl, origin, ImVec2(origin.x + width, origin.y + totalH), g);
|
||||||
|
|
||||||
const float leftX = origin.x + padIn;
|
const float leftX = origin.x + padIn;
|
||||||
@@ -795,7 +795,7 @@ void RenderChatTab(App* app)
|
|||||||
{
|
{
|
||||||
ImDrawList* paneDL = ImGui::GetWindowDrawList();
|
ImDrawList* paneDL = ImGui::GetWindowDrawList();
|
||||||
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
||||||
material::GlassPanelSpec g; g.rounding = 12.0f * Layout::dpiScale(); g.fillAlpha = 20; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 20; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + listW, pMin.y + avail.y), g);
|
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + listW, pMin.y + avail.y), g);
|
||||||
}
|
}
|
||||||
// Inner padding so the list content (buttons, search, conversation cards) doesn't hug the glass
|
// Inner padding so the list content (buttons, search, conversation cards) doesn't hug the glass
|
||||||
@@ -973,7 +973,7 @@ void RenderChatTab(App* app)
|
|||||||
ImDrawList* paneDL = ImGui::GetWindowDrawList();
|
ImDrawList* paneDL = ImGui::GetWindowDrawList();
|
||||||
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
||||||
const float pW = ImGui::GetContentRegionAvail().x;
|
const float pW = ImGui::GetContentRegionAvail().x;
|
||||||
material::GlassPanelSpec g; g.rounding = 12.0f * tdp; g.fillAlpha = 12; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 12; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + pW, pMin.y + (avail.y - composerAreaH)), g);
|
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + pW, pMin.y + (avail.y - composerAreaH)), g);
|
||||||
}
|
}
|
||||||
// Inner padding so the header + messages don't hug the glass card's edges (the message child
|
// Inner padding so the header + messages don't hug the glass card's edges (the message child
|
||||||
@@ -1622,7 +1622,7 @@ void RenderChatTab(App* app)
|
|||||||
// FrameBg); a flat FrameBg showed the sharp texture.
|
// FrameBg); a flat FrameBg showed the sharp texture.
|
||||||
{
|
{
|
||||||
ImDrawList* cdl = ImGui::GetWindowDrawList();
|
ImDrawList* cdl = ImGui::GetWindowDrawList();
|
||||||
material::GlassPanelSpec g; g.rounding = 8.0f * tdp; g.fillAlpha = 16; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 16; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(cdl, ImVec2(inputX, rowY),
|
material::DrawGlassPanel(cdl, ImVec2(inputX, rowY),
|
||||||
ImVec2(inputX + inputW, rowY + composerBoxH), g);
|
ImVec2(inputX + inputW, rowY + composerBoxH), g);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1026,7 +1026,7 @@ void RenderContactsTab(App* app)
|
|||||||
const float tpW = ImGui::GetContentRegionAvail().x;
|
const float tpW = ImGui::GetContentRegionAvail().x;
|
||||||
const float tIpad = 12.0f * dp, tVpad = 10.0f * dp;
|
const float tIpad = 12.0f * dp, tVpad = 10.0f * dp;
|
||||||
{
|
{
|
||||||
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 14; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(tpdl, tpMin, ImVec2(tpMin.x + tpW, tpMin.y + listH), g);
|
material::DrawGlassPanel(tpdl, tpMin, ImVec2(tpMin.x + tpW, tpMin.y + listH), g);
|
||||||
}
|
}
|
||||||
ImGui::SetCursorScreenPos(ImVec2(tpMin.x + tIpad, tpMin.y + tVpad));
|
ImGui::SetCursorScreenPos(ImVec2(tpMin.x + tIpad, tpMin.y + tVpad));
|
||||||
@@ -1129,7 +1129,7 @@ void RenderContactsTab(App* app)
|
|||||||
ImDrawList* pdl = ImGui::GetWindowDrawList();
|
ImDrawList* pdl = ImGui::GetWindowDrawList();
|
||||||
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
const ImVec2 pMin = ImGui::GetCursorScreenPos();
|
||||||
const float pW = ImGui::GetContentRegionAvail().x;
|
const float pW = ImGui::GetContentRegionAvail().x;
|
||||||
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 14; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(pdl, pMin, ImVec2(pMin.x + pW, pMin.y + listH), g);
|
material::DrawGlassPanel(pdl, pMin, ImVec2(pMin.x + pW, pMin.y + listH), g);
|
||||||
}
|
}
|
||||||
// AlwaysUseWindowPadding: a borderless child ignores WindowPadding without it, so the rows
|
// AlwaysUseWindowPadding: a borderless child ignores WindowPadding without it, so the rows
|
||||||
@@ -1390,7 +1390,7 @@ void RenderContactsTab(App* app)
|
|||||||
const float rowH = avR * 2.0f + 16.0f * dp;
|
const float rowH = avR * 2.0f + 16.0f * dp;
|
||||||
const float panelH = 2.0f * rowH + gap + 2.0f * padIn;
|
const float panelH = 2.0f * rowH + gap + 2.0f * padIn;
|
||||||
const ImVec2 pOrigin = ImGui::GetCursorScreenPos();
|
const ImVec2 pOrigin = ImGui::GetCursorScreenPos();
|
||||||
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
|
material::GlassPanelSpec g; g.rounding = Layout::glassRounding(); g.fillAlpha = 14; g.borderAlpha = 34;
|
||||||
material::DrawGlassPanel(pdl, pOrigin, ImVec2(pOrigin.x + w, pOrigin.y + panelH), g);
|
material::DrawGlassPanel(pdl, pOrigin, ImVec2(pOrigin.x + w, pOrigin.y + panelH), g);
|
||||||
const ImVec2 rmn(pOrigin.x + padIn, pOrigin.y + padIn);
|
const ImVec2 rmn(pOrigin.x + padIn, pOrigin.y + padIn);
|
||||||
const float rowW = w - 2.0f * padIn;
|
const float rowW = w - 2.0f * padIn;
|
||||||
|
|||||||
@@ -309,12 +309,10 @@ void KeyExportDialog::render(App* app)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
// Close button — centered via the shared footer helper (no divider, matching the
|
||||||
|
// dialog's prior leading-Spacing placement) instead of hand-computing the offset.
|
||||||
// Close button
|
|
||||||
float button_width = closeBtn.width;
|
float button_width = closeBtn.width;
|
||||||
float avail_width = ImGui::GetContentRegionAvail().x;
|
material::BeginOverlayDialogFooter(button_width, /*drawSeparator=*/false);
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail_width - button_width) / 2.0f);
|
|
||||||
|
|
||||||
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||||
s_open = false;
|
s_open = false;
|
||||||
|
|||||||
@@ -2149,8 +2149,7 @@ static void mktDrawPortfolio(const MktCtx& cx)
|
|||||||
ImDrawList* rdl = ImGui::GetWindowDrawList();
|
ImDrawList* rdl = ImGui::GetWindowDrawList();
|
||||||
float rowW = ImGui::GetContentRegionAvail().x; // narrows automatically if a scrollbar shows
|
float rowW = ImGui::GetContentRegionAvail().x; // narrows automatically if a scrollbar shows
|
||||||
if (vis.empty()) {
|
if (vis.empty()) {
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
material::DrawEmptyState(ICON_MD_PIE_CHART, TR("portfolio_no_entries"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("portfolio_no_entries"));
|
|
||||||
} else {
|
} else {
|
||||||
// TABLE column-header strip (style 0): a thin static header labelling the numeric columns —
|
// TABLE column-header strip (style 0): a thin static header labelling the numeric columns —
|
||||||
// a signature the card styles don't have. Uses the shared pfTableCols so it aligns with rows.
|
// a signature the card styles don't have. Uses the shared pfTableCols so it aligns with rows.
|
||||||
|
|||||||
@@ -621,11 +621,9 @@ void RenderPeersTab(App* app)
|
|||||||
if (!s_show_banned) {
|
if (!s_show_banned) {
|
||||||
// ---- Connected Peers ----
|
// ---- Connected Peers ----
|
||||||
if (!app->isConnected()) {
|
if (!app->isConnected()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_CLOUD_OFF, TR("not_connected"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("not_connected"));
|
|
||||||
} else if (state.peers.empty()) {
|
} else if (state.peers.empty()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_WIFI_TETHERING, TR("peers_no_connected"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("peers_no_connected"));
|
|
||||||
} else {
|
} else {
|
||||||
const float dp = ui::Layout::dpiScale();
|
const float dp = ui::Layout::dpiScale();
|
||||||
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg();
|
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg();
|
||||||
@@ -827,11 +825,9 @@ void RenderPeersTab(App* app)
|
|||||||
} else {
|
} else {
|
||||||
// ---- Banned Peers ----
|
// ---- Banned Peers ----
|
||||||
if (!app->isConnected()) {
|
if (!app->isConnected()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_CLOUD_OFF, TR("not_connected"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("not_connected"));
|
|
||||||
} else if (state.bannedPeers.empty()) {
|
} else if (state.bannedPeers.empty()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_BLOCK, TR("peers_no_banned"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("peers_no_banned"));
|
|
||||||
} else {
|
} else {
|
||||||
const float dp = ui::Layout::dpiScale();
|
const float dp = ui::Layout::dpiScale();
|
||||||
float rowH = capFont->LegacySize + S.drawElement("tabs.peers", "banned-row-height-padding").size * dp;
|
float rowH = capFont->LegacySize + S.drawElement("tabs.peers", "banned-row-height-padding").size * dp;
|
||||||
|
|||||||
@@ -120,8 +120,10 @@ void QRPopupDialog::render(App* app)
|
|||||||
widgets::AddressCopyField("##QRAddress", s_address);
|
widgets::AddressCopyField("##QRAddress", s_address);
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Buttons — size each to its label (so "Copy address" never clips), then center the pair.
|
// Footer — a primary "Copy address" + "Close" pair, placed via the shared design-system helper.
|
||||||
|
// Size both buttons to the wider label (so "Copy address" never clips) and hand that width to
|
||||||
|
// DialogActionFooter, which centers the pair.
|
||||||
ImFont* btnFont = S.resolveFont(actionBtn.font);
|
ImFont* btnFont = S.resolveFont(actionBtn.font);
|
||||||
ImGui::PushFont(btnFont);
|
ImGui::PushFont(btnFont);
|
||||||
const float btnPad = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f;
|
const float btnPad = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f;
|
||||||
@@ -130,14 +132,14 @@ void QRPopupDialog::render(App* app)
|
|||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
if (w_copy < actionBtn.width) w_copy = actionBtn.width;
|
if (w_copy < actionBtn.width) w_copy = actionBtn.width;
|
||||||
if (w_close < actionBtn.width) w_close = actionBtn.width;
|
if (w_close < actionBtn.width) w_close = actionBtn.width;
|
||||||
const float total_width = w_copy + w_close + ImGui::GetStyle().ItemSpacing.x;
|
const float btnW = (w_copy > w_close ? w_copy : w_close);
|
||||||
ImGui::SetCursorPosX((window_width - total_width) / 2.0f);
|
|
||||||
|
|
||||||
if (material::TactileButton(TR("copy_address"), ImVec2(w_copy, 0), btnFont)) {
|
bool doCopy = false, doClose = false;
|
||||||
|
material::DialogActionFooter(TR("copy_address"), true, TR("close"), doCopy, doClose, btnW);
|
||||||
|
if (doCopy) {
|
||||||
ImGui::SetClipboardText(s_address.c_str());
|
ImGui::SetClipboardText(s_address.c_str());
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
if (doClose) {
|
||||||
if (material::TactileButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
material::EndOverlayDialog();
|
material::EndOverlayDialog();
|
||||||
|
|||||||
@@ -224,6 +224,10 @@ void RequestPaymentDialog::render(App* app)
|
|||||||
if (w_uri < actionBtn.width) w_uri = actionBtn.width;
|
if (w_uri < actionBtn.width) w_uri = actionBtn.width;
|
||||||
if (w_addr < actionBtn.width) w_addr = actionBtn.width;
|
if (w_addr < actionBtn.width) w_addr = actionBtn.width;
|
||||||
|
|
||||||
|
// Center the two-button copy row via the shared helper (no extra divider; the Separator
|
||||||
|
// above already frames this region). Total = both button widths + the ItemSpacing between.
|
||||||
|
material::BeginOverlayDialogFooter(w_uri + w_addr + ImGui::GetStyle().ItemSpacing.x, false);
|
||||||
|
|
||||||
// Copy URI button
|
// Copy URI button
|
||||||
if (material::TactileButton(TR("request_copy_uri"), ImVec2(w_uri, 0), btnFont)) {
|
if (material::TactileButton(TR("request_copy_uri"), ImVec2(w_uri, 0), btnFont)) {
|
||||||
ImGui::SetClipboardText(s_payment_uri.c_str());
|
ImGui::SetClipboardText(s_payment_uri.c_str());
|
||||||
@@ -248,6 +252,9 @@ void RequestPaymentDialog::render(App* app)
|
|||||||
float w_close = ImGui::CalcTextSize(TR("close")).x + btnPad;
|
float w_close = ImGui::CalcTextSize(TR("close")).x + btnPad;
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
if (w_close < actionBtn.width) w_close = actionBtn.width;
|
if (w_close < actionBtn.width) w_close = actionBtn.width;
|
||||||
|
// Center the lone Close button via the shared helper (no extra divider — matches the
|
||||||
|
// existing tight spacing above it).
|
||||||
|
material::BeginOverlayDialogFooter(w_close, false);
|
||||||
if (material::TactileButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
|
if (material::TactileButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
|
||||||
s_open = false;
|
s_open = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,9 +184,16 @@ void ShieldDialog::render(App* app)
|
|||||||
bool sh_syncing = state.sync.syncing;
|
bool sh_syncing = state.sync.syncing;
|
||||||
bool can_submit = !s_operation_pending && s_to_address[0] != '\0' && sh_connected && !sh_syncing;
|
bool can_submit = !s_operation_pending && s_to_address[0] != '\0' && sh_connected && !sh_syncing;
|
||||||
|
|
||||||
if (!can_submit) ImGui::BeginDisabled();
|
// Center the primary + Cancel action row via the shared footer helper. We can't use
|
||||||
|
// DialogActionFooter here because the primary button carries a disabled-hover tooltip that must
|
||||||
|
// fire on ITS item (the helper draws primary+Close internally, leaving no hook between them), so
|
||||||
|
// we keep the two TactileButtons + the interleaved tooltip and only standardize the placement.
|
||||||
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
|
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
|
||||||
|
float footerBtnW = shieldBtn.width + cancelBtn.width + ImGui::GetStyle().ItemSpacing.x;
|
||||||
|
material::BeginOverlayDialogFooter(footerBtnW, /*drawSeparator=*/false);
|
||||||
|
|
||||||
|
if (!can_submit) ImGui::BeginDisabled();
|
||||||
|
|
||||||
if (material::TactileButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
|
if (material::TactileButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
|
||||||
s_operation_pending = true;
|
s_operation_pending = true;
|
||||||
s_status_message = TR("shield_submitting");
|
s_status_message = TR("shield_submitting");
|
||||||
|
|||||||
@@ -174,25 +174,18 @@ void TransactionDetailsDialog::render(App* app)
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Buttons
|
// Buttons — centered primary + Close pair via the shared footer helper.
|
||||||
float button_width = bottomBtn.width;
|
|
||||||
float total_width = button_width * 2 + ImGui::GetStyle().ItemSpacing.x;
|
|
||||||
float start_x = (ImGui::GetWindowWidth() - total_width) / 2.0f;
|
|
||||||
ImGui::SetCursorPosX(start_x);
|
|
||||||
|
|
||||||
// Guard against an empty/whitespace explorer URL so we never open a garbage link.
|
// Guard against an empty/whitespace explorer URL so we never open a garbage link.
|
||||||
std::string explorerBase = app->settings()->getTxExplorerUrl();
|
std::string explorerBase = app->settings()->getTxExplorerUrl();
|
||||||
bool explorerValid = explorerBase.find_first_not_of(" \t\r\n") != std::string::npos;
|
bool explorerValid = explorerBase.find_first_not_of(" \t\r\n") != std::string::npos;
|
||||||
if (!explorerValid) ImGui::BeginDisabled();
|
bool doExplorer = false, doClose = false;
|
||||||
if (material::StyledButton(TR("tx_view_explorer"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
material::DialogActionFooter(TR("tx_view_explorer"), explorerValid, TR("close"),
|
||||||
|
doExplorer, doClose, bottomBtn.width);
|
||||||
|
if (doExplorer) {
|
||||||
std::string url = explorerBase + tx.txid;
|
std::string url = explorerBase + tx.txid;
|
||||||
util::Platform::openUrl(url);
|
util::Platform::openUrl(url);
|
||||||
}
|
}
|
||||||
if (!explorerValid) ImGui::EndDisabled();
|
if (doClose) {
|
||||||
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
|
||||||
s_open = false;
|
s_open = false;
|
||||||
}
|
}
|
||||||
material::EndOverlayDialog();
|
material::EndOverlayDialog();
|
||||||
|
|||||||
@@ -541,32 +541,22 @@ void RenderTransactionsTab(App* app)
|
|||||||
float startX = ImGui::GetContentRegionMax().x - totalPagW;
|
float startX = ImGui::GetContentRegionMax().x - totalPagW;
|
||||||
ImGui::SetCursorPosX(startX);
|
ImGui::SetCursorPosX(startX);
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, Layout::spacingSm());
|
// First page — TactileButton (shared glass press, matches the explorer pager)
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.5f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, 15)));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, 30)));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, 45)));
|
|
||||||
|
|
||||||
// First page
|
|
||||||
ImGui::BeginDisabled(s_current_page == 0);
|
ImGui::BeginDisabled(s_current_page == 0);
|
||||||
ImGui::PushFont(Type().iconSmall());
|
if (TactileButton(ICON_MD_FIRST_PAGE "##txFirst", ImVec2(btnW, btnW), Type().iconSmall())) {
|
||||||
if (ImGui::Button(ICON_MD_FIRST_PAGE "##txFirst", ImVec2(btnW, btnW))) {
|
|
||||||
s_current_page = 0;
|
s_current_page = 0;
|
||||||
s_expanded_row = -1;
|
s_expanded_row = -1;
|
||||||
}
|
}
|
||||||
ImGui::PopFont();
|
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
ImGui::SameLine(0, gap);
|
ImGui::SameLine(0, gap);
|
||||||
|
|
||||||
// Previous page
|
// Previous page
|
||||||
ImGui::BeginDisabled(s_current_page == 0);
|
ImGui::BeginDisabled(s_current_page == 0);
|
||||||
ImGui::PushFont(Type().iconSmall());
|
if (TactileButton(ICON_MD_CHEVRON_LEFT "##txPrev", ImVec2(btnW, btnW), Type().iconSmall())) {
|
||||||
if (ImGui::Button(ICON_MD_CHEVRON_LEFT "##txPrev", ImVec2(btnW, btnW))) {
|
|
||||||
s_current_page--;
|
s_current_page--;
|
||||||
s_expanded_row = -1;
|
s_expanded_row = -1;
|
||||||
}
|
}
|
||||||
ImGui::PopFont();
|
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
ImGui::SameLine(0, gap);
|
ImGui::SameLine(0, gap);
|
||||||
@@ -590,28 +580,21 @@ void RenderTransactionsTab(App* app)
|
|||||||
|
|
||||||
// Next page
|
// Next page
|
||||||
ImGui::BeginDisabled(s_current_page >= totalPages - 1);
|
ImGui::BeginDisabled(s_current_page >= totalPages - 1);
|
||||||
ImGui::PushFont(Type().iconSmall());
|
if (TactileButton(ICON_MD_CHEVRON_RIGHT "##txNext", ImVec2(btnW, btnW), Type().iconSmall())) {
|
||||||
if (ImGui::Button(ICON_MD_CHEVRON_RIGHT "##txNext", ImVec2(btnW, btnW))) {
|
|
||||||
s_current_page++;
|
s_current_page++;
|
||||||
s_expanded_row = -1;
|
s_expanded_row = -1;
|
||||||
}
|
}
|
||||||
ImGui::PopFont();
|
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
ImGui::SameLine(0, gap);
|
ImGui::SameLine(0, gap);
|
||||||
|
|
||||||
// Last page
|
// Last page
|
||||||
ImGui::BeginDisabled(s_current_page >= totalPages - 1);
|
ImGui::BeginDisabled(s_current_page >= totalPages - 1);
|
||||||
ImGui::PushFont(Type().iconSmall());
|
if (TactileButton(ICON_MD_LAST_PAGE "##txLast", ImVec2(btnW, btnW), Type().iconSmall())) {
|
||||||
if (ImGui::Button(ICON_MD_LAST_PAGE "##txLast", ImVec2(btnW, btnW))) {
|
|
||||||
s_current_page = totalPages - 1;
|
s_current_page = totalPages - 1;
|
||||||
s_expanded_row = -1;
|
s_expanded_row = -1;
|
||||||
}
|
}
|
||||||
ImGui::PopFont();
|
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
ImGui::PopStyleVar(2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
@@ -641,20 +624,17 @@ void RenderTransactionsTab(App* app)
|
|||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
{
|
{
|
||||||
if (!app->isConnected()) {
|
if (!app->isConnected()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_CLOUD_OFF,
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
TR(app->isLiteBuild() ? "lite_no_wallet" : "not_connected"));
|
||||||
TR(app->isLiteBuild() ? "lite_no_wallet" : "not_connected"));
|
|
||||||
} else if (state.transactions.empty()) {
|
} else if (state.transactions.empty()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
|
||||||
if (txLoading) {
|
if (txLoading) {
|
||||||
snprintf(buf, sizeof(buf), "%s%s", txLoadingText.c_str(), material::LoadingDots());
|
snprintf(buf, sizeof(buf), "%s%s", txLoadingText.c_str(), material::LoadingDots());
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
|
material::DrawEmptyState(ICON_MD_HOURGLASS_EMPTY, buf);
|
||||||
} else {
|
} else {
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions"));
|
material::DrawEmptyState(ICON_MD_RECEIPT_LONG, TR("no_transactions"));
|
||||||
}
|
}
|
||||||
} else if (filtered_indices.empty()) {
|
} else if (filtered_indices.empty()) {
|
||||||
ImGui::Dummy(ImVec2(0, 20));
|
material::DrawEmptyState(ICON_MD_SEARCH_OFF, TR("no_matching"));
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_matching"));
|
|
||||||
} else {
|
} else {
|
||||||
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd();
|
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd();
|
||||||
float innerW = ImGui::GetContentRegionAvail().x;
|
float innerW = ImGui::GetContentRegionAvail().x;
|
||||||
|
|||||||
@@ -210,11 +210,10 @@ void ValidateAddressDialog::render(App* app)
|
|||||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected"));
|
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
// Close button at bottom — centered via the shared footer helper (no separator, matching
|
||||||
|
// the prior hand-rolled placement).
|
||||||
// Close button at bottom
|
|
||||||
float button_width = closeBtn.width;
|
float button_width = closeBtn.width;
|
||||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) / 2.0f);
|
material::BeginOverlayDialogFooter(button_width, /*drawSeparator=*/false);
|
||||||
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||||
s_open = false;
|
s_open = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -951,7 +951,7 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["filter"] = "Filter...";
|
strings_["filter"] = "Filter...";
|
||||||
strings_["no_addresses_yet"] = "No addresses yet";
|
strings_["no_addresses_yet"] = "No addresses yet";
|
||||||
strings_["showing_x_of_y"] = "Showing %d of %d addresses";
|
strings_["showing_x_of_y"] = "Showing %d of %d addresses";
|
||||||
strings_["set_label"] = "Set Label...";
|
strings_["set_label"] = "Set Label";
|
||||||
strings_["copied"] = "Copied!";
|
strings_["copied"] = "Copied!";
|
||||||
strings_["hidden_tag"] = " (hidden)";
|
strings_["hidden_tag"] = " (hidden)";
|
||||||
strings_["z_address"] = "Z-Address";
|
strings_["z_address"] = "Z-Address";
|
||||||
|
|||||||
Reference in New Issue
Block a user