refactor(ui): extract shared ProgressBar / CopyableTextOverlay / DrawPill helpers

UI-standardization audit: three draw patterns were copy-pasted across tabs and
dialogs. Promote each to a material:: helper and adopt at every verbatim site.
Output is pixel-identical (same colors/geometry threaded through as params).

- material::DrawProgressBar / ProgressBar — replaces 3 byte-identical rounded
  fill bars in the daemon/xmrig/bootstrap download dialogs.
- material::CopyableTextOverlay — the click-to-copy affordance (hit button +
  hand cursor + tooltip + hover underline + clipboard) shared by the peers
  best-block hash and the 3 mining stats (difficulty/block/address). Returns
  "copied this frame" so callers keep their own toast (no ui-layer dependency).
- material::DrawPill / PillSize — rounded bg + optional border + inset text
  badge, shared by the explorer status pill, the market Z/T chip, and the
  network official/custom tag.

Divergent one-offs left in place (transactions status pills use schema rounding
around pre-positioned multi-line text; the mining filter-pill is a button bg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 18:48:08 -05:00
parent 07f6d2efc9
commit ad59d62b82
9 changed files with 102 additions and 94 deletions

View File

@@ -279,6 +279,81 @@ inline ImFont* resolveButtonFont(int perButton, int sectionDefault)
return resolveButtonFont(perButton >= 0 ? perButton : sectionDefault);
}
// ── Progress / fill bar ─────────────────────────────────────────────────
// A rounded track with a fraction (0..1) fill, drawn at the current cursor and advancing the layout
// cursor past it (matches the download-dialog usage). For a placement you control, use the pMin/pMax
// overload. Defaults: theme Primary fill, a subtle white track, 4dp corners.
inline void DrawProgressBar(ImDrawList* dl, const ImVec2& pMin, const ImVec2& pMax, float frac01,
ImU32 fill = 0, ImU32 track = IM_COL32(255, 255, 255, 30), float rounding = -1.0f)
{
if (fill == 0) fill = Primary();
if (rounding < 0.0f) rounding = 4.0f * Layout::dpiScale();
frac01 = frac01 < 0.0f ? 0.0f : (frac01 > 1.0f ? 1.0f : frac01);
dl->AddRectFilled(pMin, pMax, track, rounding);
if (frac01 > 0.0f)
dl->AddRectFilled(pMin, ImVec2(pMin.x + (pMax.x - pMin.x) * frac01, pMax.y), fill, rounding);
}
inline void ProgressBar(float width, float height, float frac01,
ImU32 fill = 0, ImU32 track = IM_COL32(255, 255, 255, 30))
{
ImVec2 pMin = ImGui::GetCursorScreenPos();
DrawProgressBar(ImGui::GetWindowDrawList(), pMin, ImVec2(pMin.x + width, pMin.y + height), frac01, fill, track);
ImGui::Dummy(ImVec2(0, height));
}
// ── Click-to-copy affordance ────────────────────────────────────────────
// Turns an already-drawn text run into a click-to-copy control. The caller draws the visible text
// (font/color/position of its choosing); this places an invisible hit button (textSize + hitPad) at
// `pos`, shows a hand cursor + tooltip + a hover underline, and copies `copyValue` on click. Returns
// true the frame it was clicked (already on the clipboard) so the caller can show its own toast.
// Caller manages any cursor save/restore around the call.
inline bool CopyableTextOverlay(const char* id, const ImVec2& pos, const ImVec2& textSize, float fontSize,
const char* copyValue, const char* tooltip, const ImVec2& hitPad)
{
const float dp = Layout::dpiScale();
ImGui::SetCursorScreenPos(pos);
ImGui::InvisibleButton(id, ImVec2(textSize.x + hitPad.x, fontSize + hitPad.y));
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (tooltip && tooltip[0]) Tooltip("%s", tooltip);
const float uy = pos.y + fontSize + 1.0f * dp;
ImGui::GetWindowDrawList()->AddLine(ImVec2(pos.x, uy), ImVec2(pos.x + textSize.x, uy),
WithAlpha(OnSurface(), 60), 1.0f * dp);
}
bool copied = false;
if (ImGui::IsItemClicked()) {
ImGui::SetClipboardText(copyValue);
copied = true;
}
return copied;
}
// ── Pill / badge ─────────────────────────────────────────────────────────
// A rounded chip: a filled background sized to `text` + `pad`, an optional 1px border, and the text
// inset by `pad`. Use PillSize first when you need to right/center-anchor (compute the width, place
// the pill, then draw).
inline ImVec2 PillSize(const char* text, ImFont* font, const ImVec2& pad)
{
ImVec2 t = font->CalcTextSizeA(font->LegacySize, FLT_MAX, 0.0f, text);
return ImVec2(t.x + pad.x * 2.0f, font->LegacySize + pad.y * 2.0f);
}
// Draw a pill/badge anchored at top-left `pos`. `border` == 0 draws no outline. rounding < 0 => fully
// rounded (height/2). Returns the pill's total size.
inline ImVec2 DrawPill(ImDrawList* dl, const ImVec2& pos, const char* text, ImFont* font,
ImU32 fg, ImU32 bg, ImU32 border = 0,
const ImVec2& pad = ImVec2(4.0f, 2.0f), float rounding = -1.0f)
{
ImVec2 sz = PillSize(text, font, pad);
if (rounding < 0.0f) rounding = sz.y * 0.5f;
ImVec2 pMax(pos.x + sz.x, pos.y + sz.y);
dl->AddRectFilled(pos, pMax, bg, rounding);
if (border != 0) dl->AddRect(pos, pMax, border, rounding, 0, 1.0f * Layout::dpiScale());
dl->AddText(font, font->LegacySize, ImVec2(pos.x + pad.x, pos.y + pad.y), fg, text);
return sz;
}
// ── Tactile wrappers ────────────────────────────────────────────────────
// Drop-in replacements for ImGui::Button / SmallButton that automatically
// add the tactile highlight overlay after rendering.