// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // Shared "click-to-copy field" widgets used by the key-export and QR-popup dialogs, so an address // renders identically in both. A field is a bordered box of wrapping text whose whole area copies // to the clipboard on click; long secrets/addresses are grouped into space-separated blocks. #pragma once #include #include #include "imgui.h" #include "../material/colors.h" #include "../material/tooltip_style.h" #include "../../util/i18n.h" namespace dragonx { namespace ui { namespace widgets { // Draw `text` wrapped within wrapW with every visual line horizontally centered (returns the height // used). ImGui has no center+wrap, so we walk the wrap points the way it would. inline float DrawCenteredWrapped(ImDrawList* dl, ImFont* font, float fs, ImVec2 origin, float wrapW, ImU32 col, const char* text) { const float scale = fs / font->LegacySize; const float lineH = ImGui::GetTextLineHeight(); const char* s = text; const char* e = text + std::strlen(text); float y = origin.y; while (s < e) { const char* wrap = font->CalcWordWrapPositionA(scale, s, e, wrapW); if (wrap <= s) wrap = s + 1; const ImVec2 lw = font->CalcTextSizeA(fs, 1.0e30f, 0.0f, s, wrap); float lx = origin.x + (wrapW - lw.x) * 0.5f; if (lx < origin.x) lx = origin.x; dl->AddText(font, fs, ImVec2(lx, y), col, s, wrap); y += lineH; s = wrap; while (s < e && (*s == ' ' || *s == '\n')) s++; // ImGui consumes the break char } return y - origin.y; } // Group a long string into space-separated blocks so it reads in scannable chunks and wraps on // block boundaries. Callers copy the ORIGINAL (un-grouped) string. inline std::string ChunkString(const std::string& s, size_t group) { if (group == 0 || s.size() <= group) return s; std::string out; out.reserve(s.size() + s.size() / group + 1); for (size_t i = 0; i < s.size(); ++i) { if (i && (i % group) == 0) out.push_back(' '); out.push_back(s[i]); } return out; } // A wrapping, click-to-copy "field": draws `display` in a bordered box (the whole box is the copy // affordance) and returns true when clicked. `center` centers each wrapped line. The caller is // responsible for the actual clipboard write (so secrets can use an auto-clearing clipboard). inline bool CopyField(const char* id, const std::string& display, float width, float minH, bool center) { namespace m = material; ImDrawList* dl = ImGui::GetWindowDrawList(); ImGuiStyle& st = ImGui::GetStyle(); ImFont* font = ImGui::GetFont(); const float fs = ImGui::GetFontSize(); const float padX = st.FramePadding.x + 4.0f; const float padY = st.FramePadding.y + 4.0f; const float wrapW = (width - padX * 2.0f) > 1.0f ? (width - padX * 2.0f) : 1.0f; const ImVec2 ts = ImGui::CalcTextSize(display.c_str(), nullptr, false, wrapW); float h = ts.y + padY * 2.0f; if (h < minH) h = minH; const ImVec2 p0 = ImGui::GetCursorScreenPos(); const ImVec2 p1 = ImVec2(p0.x + width, p0.y + h); ImGui::InvisibleButton(id, ImVec2(width, h)); const bool hovered = ImGui::IsItemHovered(); const bool clicked = ImGui::IsItemClicked(); dl->AddRectFilled(p0, p1, m::WithAlpha(m::OnSurface(), hovered ? 18 : 8), 6.0f); dl->AddRect(p0, p1, m::WithAlpha(m::OnSurface(), hovered ? 70 : 30), 6.0f, 0, 1.0f); const float textY = p0.y + (h - ts.y) * 0.5f; // vertical center if (center) DrawCenteredWrapped(dl, font, fs, ImVec2(p0.x + padX, textY), wrapW, m::OnSurface(), display.c_str()); else dl->AddText(font, fs, ImVec2(p0.x + padX, textY), m::OnSurface(), display.c_str(), nullptr, wrapW); if (hovered) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); material::Tooltip("%s", TR("copy")); } return clicked; } // An address field: chunked by 4 for readability, width clamped to its text, box centered in the // available region. Copies the raw address to the clipboard on click; returns true when clicked. inline bool AddressCopyField(const char* id, const std::string& address) { const std::string at = ChunkString(address, 4); const float avail = ImGui::GetContentRegionAvail().x; const float padX = ImGui::GetStyle().FramePadding.x + 4.0f; float boxW = ImGui::CalcTextSize(at.c_str()).x + padX * 2.0f + 2.0f; // clamp to text width if (boxW > avail) boxW = avail; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail > boxW ? (avail - boxW) * 0.5f : 0.0f)); const bool clicked = CopyField(id, at, boxW, 0.0f, /*center=*/true); if (clicked) ImGui::SetClipboardText(address.c_str()); return clicked; } } // namespace widgets } // namespace ui } // namespace dragonx