diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index bcb46ac..32c8a9a 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -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. diff --git a/src/ui/windows/bootstrap_download_dialog.h b/src/ui/windows/bootstrap_download_dialog.h index 37775e9..e2bab61 100644 --- a/src/ui/windows/bootstrap_download_dialog.h +++ b/src/ui/windows/bootstrap_download_dialog.h @@ -162,18 +162,7 @@ private: ImGui::Spacing(); // Progress bar - float barH = 8.0f * dp; - float barW = ImGui::GetContentRegionAvail().x; - ImVec2 barMin = ImGui::GetCursorScreenPos(); - ImVec2 barMax(barMin.x + barW, barMin.y + barH); - ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddRectFilled(barMin, barMax, IM_COL32(255, 255, 255, 30), 4.0f * dp); - float fillW = barW * (prog.percent / 100.0f); - if (fillW > 0) { - dl->AddRectFilled(barMin, ImVec2(barMin.x + fillW, barMax.y), - Primary(), 4.0f * dp); - } - ImGui::Dummy(ImVec2(0, barH)); + material::ProgressBar(ImGui::GetContentRegionAvail().x, 8.0f * dp, prog.percent / 100.0f); ImGui::Spacing(); // Percent + status text diff --git a/src/ui/windows/daemon_download_dialog.h b/src/ui/windows/daemon_download_dialog.h index 04bab0b..457a7b9 100644 --- a/src/ui/windows/daemon_download_dialog.h +++ b/src/ui/windows/daemon_download_dialog.h @@ -189,16 +189,7 @@ private: : TR("daemon_update_installing"); Type().text(TypeStyle::Subtitle2, title); ImGui::Spacing(); - const float barH = 8.0f * dp; - const float barW = fullW(); - const ImVec2 bMin = ImGui::GetCursorScreenPos(); - const ImVec2 bMax(bMin.x + barW, bMin.y + barH); - ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddRectFilled(bMin, bMax, IM_COL32(255, 255, 255, 30), 4.0f * dp); - const float fillW = barW * (p.percent / 100.0f); - if (fillW > 0) - dl->AddRectFilled(bMin, ImVec2(bMin.x + fillW, bMax.y), Primary(), 4.0f * dp); - ImGui::Dummy(ImVec2(0, barH)); + material::ProgressBar(fullW(), 8.0f * dp, p.percent / 100.0f); ImGui::Spacing(); ImGui::Text("%s", p.status_text.c_str()); ImGui::Spacing(); diff --git a/src/ui/windows/explorer_tab.cpp b/src/ui/windows/explorer_tab.cpp index 41790b9..b357fd6 100644 --- a/src/ui/windows/explorer_tab.cpp +++ b/src/ui/windows/explorer_tab.cpp @@ -600,17 +600,11 @@ static void renderChainStats(App* app, float availWidth) { float maxTextW = cardW * 0.34f; statusText = truncateHashToFit(statusText, capFont, maxTextW); - ImVec2 textSz = capFont->CalcTextSizeA(capFont->LegacySize, 1000.0f, 0.0f, statusText.c_str()); - float pillPadX = Layout::spacingSm(); - float pillH = capFont->LegacySize + Layout::spacingXs() * 2.0f; - float pillW = textSz.x + pillPadX * 2.0f; - ImVec2 pillMin(cardMin.x + cardW - pad - pillW, cardMin.y + pad * 0.5f); - ImVec2 pillMax(pillMin.x + pillW, pillMin.y + pillH); - - dl->AddRectFilled(pillMin, pillMax, WithAlpha(pillCol, 32), pillH * 0.5f); - dl->AddRect(pillMin, pillMax, WithAlpha(pillCol, 110), pillH * 0.5f, 0, 1.0f * dp); - dl->AddText(capFont, capFont->LegacySize, - ImVec2(pillMin.x + pillPadX, pillMin.y + Layout::spacingXs()), pillCol, statusText.c_str()); + ImVec2 pillPad(Layout::spacingSm(), Layout::spacingXs()); + ImVec2 pillSz = material::PillSize(statusText.c_str(), capFont, pillPad); + ImVec2 pillMin(cardMin.x + cardW - pad - pillSz.x, cardMin.y + pad * 0.5f); + material::DrawPill(dl, pillMin, statusText.c_str(), capFont, pillCol, + WithAlpha(pillCol, 32), WithAlpha(pillCol, 110), pillPad); }; auto drawChainTip = [&](const ImVec2& cardMin, float cardW) { diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index e6de22c..7b573a5 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -654,10 +654,9 @@ static void pfDrawAddressSection(App* app) bool isZ = (a.type == "shielded"); const char* chip = isZ ? "Z" : "T"; ImU32 chipCol = isZ ? Success() : Warning(); - float chipW = capF->CalcTextSizeA(capF->LegacySize, FLT_MAX, 0, chip).x + 8.0f * dp; - ldl->AddRectFilled(ImVec2(rx, rcy - capF->LegacySize * 0.5f - 2.0f), - ImVec2(rx + chipW, rcy + capF->LegacySize * 0.5f + 2.0f), WithAlpha(chipCol, 40), 4.0f * dp); - ldl->AddText(capF, capF->LegacySize, ImVec2(rx + 4.0f * dp, rcy - capF->LegacySize * 0.5f), chipCol, chip); + ImVec2 chipPos(rx, rcy - capF->LegacySize * 0.5f - 2.0f); + float chipW = material::DrawPill(ldl, chipPos, chip, capF, chipCol, + WithAlpha(chipCol, 40), 0, ImVec2(4.0f * dp, 2.0f), 4.0f * dp).x; rx += chipW + Layout::spacingSm(); std::string aicon = app->getAddressIcon(a.address); if (!aicon.empty()) { diff --git a/src/ui/windows/mining_earnings.cpp b/src/ui/windows/mining_earnings.cpp index 0702e77..8c10471 100644 --- a/src/ui/windows/mining_earnings.cpp +++ b/src/ui/windows/mining_earnings.cpp @@ -283,17 +283,9 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& snprintf(buf, sizeof(buf), "%.4f", mining.difficulty); dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X + valOffX, cy), OnSurface(), buf); ImVec2 diffSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf); - ImGui::SetCursorScreenPos(ImVec2(col1X + valOffX, cy)); - ImGui::InvisibleButton("##DiffCopy", ImVec2(diffSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp)); - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - material::Tooltip("%s", TR("mining_click_copy_difficulty")); - dl->AddLine(ImVec2(col1X + valOffX, cy + capFont->LegacySize + 1 * dp), - ImVec2(col1X + valOffX + diffSz.x, cy + capFont->LegacySize + 1 * dp), - WithAlpha(OnSurface(), 60), 1.0f * dp); - } - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(buf); + if (material::CopyableTextOverlay("##DiffCopy", ImVec2(col1X + valOffX, cy), diffSz, + capFont->LegacySize, buf, TR("mining_click_copy_difficulty"), + ImVec2(Layout::spacingMd(), 4 * dp))) { Notifications::instance().info(TR("mining_difficulty_copied")); } } @@ -304,17 +296,9 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& snprintf(buf, sizeof(buf), "%d", mining.blocks); dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X + valOffX, cy), OnSurface(), buf); ImVec2 blkSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf); - ImGui::SetCursorScreenPos(ImVec2(col2X + valOffX, cy)); - ImGui::InvisibleButton("##BlockCopy", ImVec2(blkSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp)); - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - material::Tooltip("%s", TR("mining_click_copy_block")); - dl->AddLine(ImVec2(col2X + valOffX, cy + capFont->LegacySize + 1 * dp), - ImVec2(col2X + valOffX + blkSz.x, cy + capFont->LegacySize + 1 * dp), - WithAlpha(OnSurface(), 60), 1.0f * dp); - } - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(buf); + if (material::CopyableTextOverlay("##BlockCopy", ImVec2(col2X + valOffX, cy), blkSz, + capFont->LegacySize, buf, TR("mining_click_copy_block"), + ImVec2(Layout::spacingMd(), 4 * dp))) { Notifications::instance().info(TR("mining_block_copied")); } } @@ -342,17 +326,10 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurface(), truncAddr.c_str()); ImVec2 addrTextSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, truncAddr.c_str()); - ImGui::SetCursorScreenPos(ImVec2(col3X + valOffX, cy)); - ImGui::InvisibleButton("##MiningAddrCopy", ImVec2(addrTextSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp)); - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - material::Tooltip("%s", TR("mining_click_copy_address")); - dl->AddLine(ImVec2(col3X + valOffX, cy + capFont->LegacySize + 1 * dp), - ImVec2(col3X + valOffX + addrTextSz.x, cy + capFont->LegacySize + 1 * dp), - WithAlpha(OnSurface(), 60), 1.0f * dp); - } - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(mining_address.c_str()); + if (material::CopyableTextOverlay("##MiningAddrCopy", ImVec2(col3X + valOffX, cy), addrTextSz, + capFont->LegacySize, mining_address.c_str(), + TR("mining_click_copy_address"), + ImVec2(Layout::spacingMd(), 4 * dp))) { Notifications::instance().info(TR("mining_address_copied")); } } diff --git a/src/ui/windows/network_tab.cpp b/src/ui/windows/network_tab.cpp index 0eae5ac..b93ae31 100644 --- a/src/ui/windows/network_tab.cpp +++ b/src/ui/windows/network_tab.cpp @@ -285,9 +285,8 @@ void RenderLiteNetworkTab(App* app) ImVec2 tagSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, tag); float tagX = rx - tagSz.x; float tagY = cardMin.y + 9.0f * dp; - dl->AddRectFilled(ImVec2(tagX - 6 * dp, tagY - 1 * dp), ImVec2(rx + 6 * dp, tagY + tagSz.y + 1 * dp), - tagBg, 4.0f * dp); - dl->AddText(cap, cap->LegacySize, ImVec2(tagX, tagY), tagFg, tag); + material::DrawPill(dl, ImVec2(tagX - 6 * dp, tagY - 1 * dp), tag, cap, tagFg, tagBg, 0, + ImVec2(6 * dp, 1 * dp), 4.0f * dp); if (selected) { const char* inUse = TR("lite_net_in_use"); ImVec2 uSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, inUse); diff --git a/src/ui/windows/peers_tab.cpp b/src/ui/windows/peers_tab.cpp index 6de74f6..d8ed638 100644 --- a/src/ui/windows/peers_tab.cpp +++ b/src/ui/windows/peers_tab.cpp @@ -314,17 +314,10 @@ void RenderPeersTab(App* app) // Click to copy full hash ImVec2 hashSz = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, truncHash.c_str()); ImVec2 savedCursor = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(ImVec2(cx, valY)); - ImGui::InvisibleButton("##BestBlockCopy", ImVec2(hashSz.x + Layout::spacingSm(), sub1->LegacySize + 2 * dp)); - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - material::Tooltip("%s %s", TR("peers_click_copy"), hash.c_str()); - dl->AddLine(ImVec2(cx, valY + sub1->LegacySize + 1 * dp), - ImVec2(cx + hashSz.x, valY + sub1->LegacySize + 1 * dp), - WithAlpha(OnSurface(), 60), 1.0f * dp); - } - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(hash.c_str()); + std::string copyTip = std::string(TR("peers_click_copy")) + " " + hash; + if (material::CopyableTextOverlay("##BestBlockCopy", ImVec2(cx, valY), hashSz, + sub1->LegacySize, hash.c_str(), copyTip.c_str(), + ImVec2(Layout::spacingSm(), 2 * dp))) { ui::Notifications::instance().info(TR("peers_hash_copied")); } ImGui::SetCursorScreenPos(savedCursor); diff --git a/src/ui/windows/xmrig_download_dialog.h b/src/ui/windows/xmrig_download_dialog.h index 73d3ca9..c31581b 100644 --- a/src/ui/windows/xmrig_download_dialog.h +++ b/src/ui/windows/xmrig_download_dialog.h @@ -182,16 +182,7 @@ private: : TR("xmrig_installing"); Type().text(TypeStyle::Subtitle2, title); ImGui::Spacing(); - const float barH = 8.0f * dp; - const float barW = fullW(); - const ImVec2 bMin = ImGui::GetCursorScreenPos(); - const ImVec2 bMax(bMin.x + barW, bMin.y + barH); - ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddRectFilled(bMin, bMax, IM_COL32(255, 255, 255, 30), 4.0f * dp); - const float fillW = barW * (p.percent / 100.0f); - if (fillW > 0) - dl->AddRectFilled(bMin, ImVec2(bMin.x + fillW, bMax.y), Primary(), 4.0f * dp); - ImGui::Dummy(ImVec2(0, barH)); + material::ProgressBar(fullW(), 8.0f * dp, p.percent / 100.0f); ImGui::Spacing(); ImGui::Text("%s", p.status_text.c_str()); ImGui::Spacing();