// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "qr_popup_dialog.h" #include "../../app.h" #include "../../util/i18n.h" #include "../widgets/qr_code.h" #include "../widgets/copy_field.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" #include "../layout.h" #include "../theme.h" #include "imgui.h" namespace dragonx { namespace ui { // Static member initialization bool QRPopupDialog::s_open = false; std::string QRPopupDialog::s_address; std::string QRPopupDialog::s_label; uintptr_t QRPopupDialog::s_qr_texture = 0; int QRPopupDialog::s_qr_width = 0; int QRPopupDialog::s_qr_height = 0; void QRPopupDialog::show(const std::string& address, const std::string& label) { // Cleanup previous texture if (s_qr_texture != 0) { FreeQRTexture(s_qr_texture); s_qr_texture = 0; } s_open = true; s_address = address; s_label = label; // Generate new QR texture s_qr_texture = GenerateQRTexture(address.c_str(), &s_qr_width, &s_qr_height); } bool QRPopupDialog::isOpen() { return s_open; } void QRPopupDialog::close() { s_open = false; if (s_qr_texture != 0) { FreeQRTexture(s_qr_texture); s_qr_texture = 0; } } void QRPopupDialog::render(App* app) { (void)app; // Unused for now if (!s_open) return; auto& S = schema::UI(); auto qr = S.drawElement("dialogs.qr-popup", "qr-code"); auto actionBtn = S.button("dialogs.qr-popup", "action-button"); // Size the card to the address's own natural (chunked) box width — the widest content element — so // AddressCopyField fills the card under its label instead of floating centered in an over-wide card // (matches the key-export dialog). Adapts to address type + font scale; bounded to 85% of the window. const float dp = Layout::dpiScale(); const std::string chunkedAddr = widgets::ChunkString(s_address, 4); const float addrPadX = ImGui::GetStyle().FramePadding.x + 4.0f; // matches AddressCopyField const float addrBoxW = ImGui::CalcTextSize(chunkedAddr.c_str()).x + addrPadX * 2.0f + 2.0f; // The BlurFloat overlay card insets content by 28px per side (draw_helpers BeginOverlayDialog); add // that (+ a little slack) so the address box fits on one line under its label instead of wrapping. const float wantW = addrBoxW + 28.0f * 2.0f + 12.0f; const float maxW = 0.85f * ImGui::GetMainViewport()->Size.x; const float cardW = (wantW < maxW ? wantW : maxW) / dp; material::OverlayDialogSpec ov; ov.title = TR("qr_title"); ov.p_open = &s_open; ov.style = material::OverlayStyle::BlurFloat; ov.cardWidth = cardW; ov.idSuffix = "qrpopup"; if (material::BeginOverlayDialog(ov)) { // Label if present if (!s_label.empty()) { ImGui::TextWrapped("%s", s_label.c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); } // Center the QR code (responsive — larger, to suit the wider modal). float window_width = ImGui::GetWindowWidth(); float qr_size = (qr.size > 0 ? (float)qr.size : 280.0f) * dp; // schema/fallback are logical px const float responsive = window_width * 0.5f; // window_width is already dp-scaled if (responsive > qr_size) qr_size = responsive; if (qr_size > 420.0f * dp) qr_size = 420.0f * dp; float padding = (window_width - qr_size) / 2.0f; if (padding < 0.0f) padding = 0.0f; ImGui::SetCursorPosX(padding); // Render QR code if (s_qr_texture != 0) { ImGui::Image((ImTextureID)s_qr_texture, ImVec2(qr_size, qr_size)); } else { // Fallback: show error ImGui::BeginChild("QRPlaceholder", ImVec2(qr_size, qr_size), true); ImGui::TextWrapped("%s", TR("qr_failed")); ImGui::EndChild(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Address — click-to-copy field (same formatting as the key-export dialog). ImGui::Text("%s", TR("address_label")); widgets::AddressCopyField("##QRAddress", s_address); ImGui::Spacing(); // Footer — a primary "Copy address" + "Close" pair, placed via the shared design-system helper. // Size both buttons to the wider label (so "Copy address" never clips) and hand that width to // DialogActionFooter, which centers the pair. ImFont* btnFont = S.resolveFont(actionBtn.font); ImGui::PushFont(btnFont); const float btnPad = ImGui::GetStyle().FramePadding.x * 2.0f + 24.0f; float w_copy = ImGui::CalcTextSize(TR("copy_address")).x + btnPad; float w_close = ImGui::CalcTextSize(TR("close")).x + btnPad; ImGui::PopFont(); if (w_copy < actionBtn.width) w_copy = actionBtn.width; if (w_close < actionBtn.width) w_close = actionBtn.width; const float btnW = (w_copy > w_close ? w_copy : w_close); bool doCopy = false, doClose = false; material::DialogActionFooter(TR("copy_address"), true, TR("close"), doCopy, doClose, btnW); if (doCopy) { ImGui::SetClipboardText(s_address.c_str()); } if (doClose) { close(); } material::EndOverlayDialog(); } // Handle window close button if (!s_open && s_qr_texture != 0) { FreeQRTexture(s_qr_texture); s_qr_texture = 0; } } } // namespace ui } // namespace dragonx