Files
ObsidianDragon/src/ui/windows/address_label_dialog.h
DanS 9edab31728 Refactor app services and stabilize refresh/UI flows
- Add refresh scheduler and network refresh service boundaries for typed
  refresh results, ordered RPC collectors, applicators, and price parsing.
- Add daemon lifecycle and wallet security workflow helpers while preserving
  App-owned command RPC, decrypt, cancellation, and UI handoff behavior.
- Split balance, console, mining, amount formatting, and async task logic into
  focused modules with expanded Phase 4 test coverage.
- Fix market price loading by triggering price refresh immediately, avoiding
  queue-pressure drops, tracking loading/error state, and adding translations.
- Polish send, explorer, peers, settings, theme/schema, and related tab UI.
- Replace checked-in generated language headers with build-generated resources.
- Document the cleanup audit, UI static-state guidance, and architecture updates.
2026-04-29 12:47:57 -05:00

255 lines
10 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
#include <cctype>
#include <cstring>
#include <cstddef>
#include "../../app.h"
#include "../../util/i18n.h"
#include "../material/draw_helpers.h"
#include "../material/project_icons.h"
#include "../theme.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
class AddressLabelDialog {
public:
static bool drawIconByName(ImDrawList* dl,
const std::string& name,
ImVec2 center,
float /*emSize*/,
ImU32 color,
ImFont* iconFont,
float iconFontSize) {
return material::project_icons::drawByName(dl, name, center, color, iconFont, iconFontSize);
}
static void show(App* app, const std::string& address, bool isZ) {
s_open = true;
s_app = app;
s_address = address;
s_isZ = isZ;
s_selectedIcon = -1;
s_iconSearch[0] = '\0';
// Pre-fill from existing metadata
std::string existing = app->getAddressLabel(address);
std::strncpy(s_label, existing.c_str(), sizeof(s_label) - 1);
s_label[sizeof(s_label) - 1] = '\0';
std::string existingIcon = app->getAddressIcon(address);
for (int i = 0; i < material::project_icons::walletIconCount(); ++i) {
if (material::project_icons::walletIconName(i) == existingIcon) {
s_selectedIcon = i;
break;
}
}
}
static void render() {
if (!s_open || !s_app) return;
using namespace material;
constexpr float cardTopViewportRatio = 0.15f;
constexpr float cardBottomViewportRatio = 0.80f;
if (BeginOverlayDialog(TR("set_label"), &s_open, 660.0f, 0.92f, cardBottomViewportRatio)) {
float dp = Layout::dpiScale();
// Address preview
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
s_isZ ? TR("shielded_address") : TR("transparent_address"));
ImGui::Spacing();
{
std::string display = s_address;
if (display.size() > 42) display = display.substr(0, 20) + "..." + display.substr(display.size() - 16);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), display.c_str());
}
ImGui::Spacing();
ImGui::Spacing();
// Label input
Type().text(TypeStyle::Subtitle2, TR("label"));
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##AddrLabel", TR("label_placeholder"), s_label, sizeof(s_label));
ImGui::Spacing();
ImGui::Spacing();
// Icon picker grid
Type().text(TypeStyle::Subtitle2, TR("choose_icon"));
ImGui::Spacing();
// Search bar
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##IconSearch", TR("search_icons"), s_iconSearch, sizeof(s_iconSearch));
ImGui::Spacing();
ImFont* iconFont = Type().iconMed();
float iconFsz = ScaledFontSize(iconFont);
float cellSz = iconFsz + 16.0f * dp;
float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1, (int)(avail / (cellSz + 4.0f * dp)));
const char* cancelLabel = TR("cancel");
const char* saveLabel = TR("save");
ImFont* buttonFont = Type().button();
float buttonFontSize = ScaledFontSize(buttonFont);
const ImGuiStyle& style = ImGui::GetStyle();
const float cardHeight = ImGui::GetMainViewport()->Size.y * (cardBottomViewportRatio - cardTopViewportRatio);
const float bottomPadding = style.WindowPadding.y;
const bool showClearIcon = (s_selectedIcon >= 0);
const float clearRowH = showClearIcon ? ImGui::GetTextLineHeight() : 0.0f;
const float buttonRowH = ImGui::GetFrameHeight();
const float separatorH = 1.0f;
const float preButtonReserve =
(showClearIcon ? (style.ItemSpacing.y + clearRowH) : 0.0f) +
style.ItemSpacing.y * 3.0f + separatorH;
const float buttonY = cardHeight - bottomPadding - buttonRowH;
// Build filtered index list
std::vector<int> visible;
visible.reserve(material::project_icons::walletIconCount());
{
// Simple case-insensitive substring match on icon name
std::string needle(s_iconSearch);
for (char& c : needle) c = (char)std::tolower((unsigned char)c);
for (int i = 0; i < material::project_icons::walletIconCount(); ++i) {
const char* iconName = material::project_icons::walletIconName(i);
if (needle.empty() || std::strstr(iconName, needle.c_str()) != nullptr)
visible.push_back(i);
}
}
// The grid owns all vertical space above the bottom action band.
const float gridStartY = ImGui::GetCursorPosY();
const float controlsTopY = std::max(gridStartY + cellSz * 2.0f, buttonY - preButtonReserve);
const float gridMaxH = std::max(cellSz * 2.0f, controlsTopY - gridStartY);
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0));
ImGui::BeginChild("##IconGrid", ImVec2(avail, gridMaxH), ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar);
ImDrawList* dl = ImGui::GetWindowDrawList();
if (visible.empty()) {
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_icons_found"));
}
int col = 0;
for (int vi = 0; vi < (int)visible.size(); ++vi) {
int i = visible[vi];
const char* iconName = material::project_icons::walletIconName(i);
if (col != 0) ImGui::SameLine(0, 4.0f * dp);
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 mn = pos;
ImVec2 mx(pos.x + cellSz, pos.y + cellSz);
bool hov = ImGui::IsMouseHoveringRect(mn, mx);
bool sel = (s_selectedIcon == i);
// Background
if (sel) {
dl->AddRectFilled(mn, mx, WithAlpha(Primary(), 40), 6.0f * dp);
dl->AddRect(mn, mx, WithAlpha(Primary(), 120), 6.0f * dp, 0, 1.5f * dp);
} else if (hov) {
dl->AddRectFilled(mn, mx, IM_COL32(255, 255, 255, 20), 6.0f * dp);
}
// Icon centered in cell
drawIconByName(dl,
iconName,
ImVec2(mn.x + cellSz * 0.5f, mn.y + cellSz * 0.5f),
iconFsz,
sel ? Primary() : (hov ? OnSurface() : OnSurfaceMedium()),
iconFont,
iconFsz);
ImGui::PushID(i);
ImGui::InvisibleButton("##icon", ImVec2(cellSz, cellSz));
if (ImGui::IsItemClicked()) s_selectedIcon = i;
if (hov) ImGui::SetTooltip("%s", iconName);
ImGui::PopID();
col = (col + 1) % cols;
}
ImGui::EndChild();
ImGui::PopStyleColor();
if (ImGui::GetCursorPosY() < controlsTopY) {
ImGui::SetCursorPosY(controlsTopY);
}
// "No icon" option
if (showClearIcon) {
ImGui::Spacing();
if (ImGui::SmallButton(TR("clear_icon"))) {
s_selectedIcon = -1;
}
}
ImGui::Spacing();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::GetCursorPosY() < buttonY) {
ImGui::SetCursorPosY(buttonY);
}
// Buttons
float minBtnW = 120.0f * dp;
float buttonPadW = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f * dp;
float cancelW = std::max(minBtnW,
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, cancelLabel).x + buttonPadW);
float saveW = std::max(minBtnW,
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, saveLabel).x + buttonPadW);
float totalW = cancelW + saveW + Layout::spacingMd();
float rowStartX = ImGui::GetCursorPosX();
float contentW = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX(rowStartX + std::max(0.0f, (contentW - totalW) * 0.5f));
if (TactileButton(cancelLabel, ImVec2(cancelW, 0), buttonFont)) {
s_open = false;
}
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(saveLabel, ImVec2(saveW, 0), buttonFont)) {
// Apply changes
s_app->setAddressLabel(s_address, s_label);
if (s_selectedIcon >= 0)
s_app->setAddressIcon(s_address, material::project_icons::walletIconName(s_selectedIcon));
else
s_app->setAddressIcon(s_address, "");
s_open = false;
}
EndOverlayDialog();
}
}
static bool isOpen() { return s_open; }
private:
static inline bool s_open = false;
static inline App* s_app = nullptr;
static inline std::string s_address;
static inline bool s_isZ = false;
static inline char s_label[128] = {};
static inline int s_selectedIcon = -1;
static inline char s_iconSearch[64] = {};
public:
// Expose for the address list to look up icon glyphs by name
static const char* iconGlyphForName(const std::string& name) {
return material::project_icons::glyphForName(name);
}
};
} // namespace ui
} // namespace dragonx