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:
2026-08-19 11:23:49 -05:00
co-authored by Claude Opus 4.8
parent a598217975
commit d5c30237d2
25 changed files with 177 additions and 126 deletions
+57
View File
@@ -79,6 +79,63 @@ inline const char* LoadingDots() {
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
// ============================================================================