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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user