refactor(audit): batch 4 — UI design-system helpers + i18n

Mechanical, behavior-preserving consolidations from the audit. Rendering
extractions were kept byte-equivalent; sites that couldn't be made identical
were left as-is (noted below).

Shared helpers:
- util::truncateMiddle(s,maxLen) / (s,front,back) in text_format.h — replaces
  6 file-local middle-ellipsis truncators + several inline substr sites
  (send/receive/transactions/balance_recent_tx/explorer + app.cpp + 3 dialogs).
  Carries the maxLen<=3 guard, fixing the latent unsigned-underflow copies.
- material::LoadingDots() in draw_helpers.h — one animated-ellipsis source for
  8 copy-pasted spinner sites (identical GetTime()*3 phase preserved).
- Reuse the existing FormatHashrate() in explorer_tab + peers_tab (dropped two
  inline hashrate ladders).
- material::DrawButtonGlassOverlay() — the glass-fill/rim/tactile overlay block
  shared by TactileButton / TactileSmallButton / schema TactileButton.
- material::CollapsibleHeader() — the invisible-button + label + chevron idiom
  (3 of 5 settings_page sites; RPC/Debug headers skipped — non-identical).
- material::GlassCardScope (RAII) — the ChannelsSplit/Indent/DrawGlassPanel/
  ChannelsMerge card scaffold (5 settings_page cards; About/send/receive skipped
  — different padding / logo interleaving).
- material::DialogWarningHeader()/DialogConfirmFooter() — warning header +
  50/50 Cancel/danger footer across the confirm dialogs; button height moved
  from a hardcoded 40px into ui.toml (components.overlay-dialog.confirm-btn-height).
- File-local enterLowSpec()/exitLowSpec() collapse the 3 low-spec snapshot copies.

i18n: wrapped hardcoded English in the shared balance render paths and the
whole Lite lifecycle/security section in TR(), with English defaults added to
loadBuiltinEnglish() (res/lang/*.json left for the translation tooling — TR
falls back to English, so output is unchanged).

Full-node + Lite build clean; ctest 1/1; hygiene clean. These are rendering
changes — the Settings page (cards/headers/confirm dialogs), all tactile
buttons, and the Lite settings section warrant a screenshot check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 17:29:12 -05:00
parent 280a71e973
commit de11850c74
21 changed files with 452 additions and 500 deletions

View File

@@ -35,6 +35,13 @@ inline ImU32 ScaleAlpha(ImU32 col, float scale) {
return (col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
}
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
inline const char* LoadingDots() {
int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;
static const char* kDots[4] = { "", ".", "..", "..." };
return kDots[n];
}
// ============================================================================
// Text Drop Shadow
// ============================================================================
@@ -210,6 +217,30 @@ inline void DrawTactileOverlay(ImDrawList* dl, const ImVec2& bMin,
dl->PopClipRect();
}
// Glass-card button surface: frosted-glass hover/idle fill (skipped while
// pressed) + rim light + tactile depth overlay. Shared by the tactile
// button wrappers. Call after computing the button rect / rounding /
// active / hovered state.
inline void DrawButtonGlassOverlay(ImDrawList* dl, const ImVec2& bMin,
const ImVec2& bMax, float rounding,
bool active, bool hovered)
{
// Frosted glass highlight — subtle fill on top
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
// Rim light
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
// Tactile depth
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
}
// Convenience: call right after any ImGui::Button() / SmallButton() call
// to add tactile depth. Uses the last item rect automatically.
inline void ApplyTactile(ImDrawList* dl = nullptr)
@@ -278,20 +309,7 @@ inline bool TactileButton(const char* label, const ImVec2& size = ImVec2(0, 0),
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
// Frosted glass highlight — subtle fill on top
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
// Rim light
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
// Tactile depth
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
return pressed;
}
@@ -306,20 +324,44 @@ inline bool TactileSmallButton(const char* label, ImFont* font = nullptr)
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
return pressed;
}
// ============================================================================
// Collapsible section header
// ============================================================================
// Renders a full-width transparent clickable header row: `label` drawn on the
// left and an expand/collapse chevron on the right. Toggles `expanded` when
// clicked and returns the (possibly toggled) state. The label font governs
// the row height (frame height) and text metrics; the chevron uses the small
// icon font. `arrowInset` shifts the chevron left of the right edge.
inline bool CollapsibleHeader(ImDrawList* dl, const char* id, const char* label,
bool& expanded, float width, ImFont* labelFont,
ImU32 labelCol, float arrowInset = 0.0f)
{
const char* arrow = expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(labelFont);
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(id, ImVec2(width, ImGui::GetFrameHeight()))) {
expanded = !expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - labelFont->LegacySize) * 0.5f;
dl->AddText(labelFont, labelFont->LegacySize, ImVec2(hdrPos.x, textY), labelCol, label);
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = labelFont;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + width - arrowW - arrowInset, textY), labelCol, arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
return expanded;
}
// ============================================================================
// Glass Panel (glassmorphism card)
// ============================================================================
@@ -434,6 +476,57 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
}
}
// ============================================================================
// Glass Card Scope (RAII) — channel-split glass-card scaffold
// ============================================================================
// Captures the current cursor as the card's top-left, splits the draw list
// into two channels (content on 1, glass on 0), insets the content by `pad`,
// and — on scope exit — appends the bottom pad, sizes the card to its content,
// draws the glass panel behind it on channel 0, merges, and advances the
// cursor past the card. `width` is the full card width, `bottomPad` the
// trailing pad. Reproduces the hand-written prologue/epilogue exactly.
//
// {
// material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
// ... card content ...
// }
struct GlassCardScope {
ImDrawList* dl;
ImVec2 cardMin;
float width;
float pad;
float bottomPad;
GlassPanelSpec spec;
GlassCardScope(ImDrawList* dl_, float width_, float pad_, float bottomPad_,
const GlassPanelSpec& spec_)
: dl(dl_), width(width_), pad(pad_), bottomPad(bottomPad_), spec(spec_)
{
cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
}
~GlassCardScope()
{
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + width, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, spec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(width, 0));
}
GlassCardScope(const GlassCardScope&) = delete;
GlassCardScope& operator=(const GlassCardScope&) = delete;
};
// ============================================================================
// Stat Card — reusable card with overline / value / subtitle + accent stripe
// ============================================================================
@@ -656,17 +749,7 @@ inline bool TactileButton(const char* label, const schema::ButtonStyle& style,
float rounding = ImGui::GetStyle().FrameRounding;
bool active = ImGui::IsItemActive();
bool hovered = ImGui::IsItemHovered();
if (!active) {
ImU32 col = hovered
? schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 12))
: schema::UI().resolveColor("var(--glass-fill)", IM_COL32(255, 255, 255, 6));
dl->AddRectFilled(bMin, bMax, col, rounding);
}
ImU32 rim = schema::UI().resolveColor("var(--rim-light)", IM_COL32(255, 255, 255, 25));
dl->AddRect(bMin, bMax,
active ? ScaleAlpha(rim, 0.6f) : rim,
rounding, 0, 1.0f);
DrawTactileOverlay(dl, bMin, bMax, rounding, active);
DrawButtonGlassOverlay(dl, bMin, bMax, rounding, active, hovered);
ImGui::PopStyleVar(styleCount);
ImGui::PopStyleColor(colorCount);
@@ -1069,6 +1152,49 @@ inline void BeginOverlayDialogFooter(float totalActionWidth, bool drawSeparator
PlaceOverlayDialogActions(totalActionWidth);
}
// ── Confirmation dialog header / footer ─────────────────────────────────
// Shared scaffolding for the "warning + Cancel/confirm" overlay dialogs.
// Warning icon + "Warning" label row, drawn in `col` (the caller owns the
// exact accent colour; new callers can omit it to use the theme warning).
// Reproduces: PushFont(iconLarge) + ICON_MD_WARNING, PopFont, SameLine,
// TextColored(label). The label text is passed in (already translated).
inline void DialogWarningHeader(const char* warningLabel, const ImVec4& col = WarningVec4())
{
ImGui::PushFont(Type().iconLarge());
ImGui::TextColored(col, ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextColored(col, "%s", warningLabel);
}
// 50/50 Cancel / confirm footer for overlay dialogs. Sets `outCancel` /
// `outConfirm` when the respective button is pressed (the caller runs the
// action bodies). When `danger`, the confirm button gets the red danger
// style. Button height comes from ui.toml (falls back to 40). Reproduces
// the hand-written footer exactly (btnW split + SameLine + optional danger
// PushStyleColor/PopStyleColor(2)).
inline void DialogConfirmFooter(const char* cancelId, const char* confirmLabel,
bool danger, bool& outCancel, bool& outConfirm)
{
float btnH = schema::UI().drawElement("components.overlay-dialog", "confirm-btn-height").sizeOr(40.0f);
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(cancelId, ImVec2(btnW, btnH))) {
outCancel = true;
}
ImGui::SameLine();
if (danger) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
}
if (ImGui::Button(confirmLabel, ImVec2(btnW, btnH))) {
outConfirm = true;
}
if (danger) {
ImGui::PopStyleColor(2);
}
}
} // namespace material
} // namespace ui
} // namespace dragonx