Network tab: glow only the active node, drop the left accent bar. Key-export dialog: fix the lite-wallet "Not connected" failure by exporting the key locally via the SDXL backend when there's no daemon; rework the layout to wrapping click-to-copy fields with a side QR (empty placeholder when hidden), 85% modal width, HRP-preserving key chunking, and a centered, emphasized warning. QR popup matched to the same sizing and click-to-copy address. Shared field rendering extracted to widgets/copy_field.h so both dialogs stay in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
4.8 KiB
C++
114 lines
4.8 KiB
C++
// 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 <string>
|
|
#include <cstring>
|
|
|
|
#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
|