// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include #include #include #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 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