// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "settings_page.h" #include "../../app.h" #include "../../version.h" #include "../../config/settings.h" #include "../../util/i18n.h" #include "../../util/platform.h" #include "../../rpc/rpc_client.h" #include "../theme.h" #include "../layout.h" #include "../schema/ui_schema.h" #include "../schema/skin_manager.h" #include "../notifications.h" #include "../effects/imgui_acrylic.h" #include "../material/draw_helpers.h" #include "../material/type.h" #include "../material/colors.h" #include "../windows/validate_address_dialog.h" #include "../windows/address_book_dialog.h" #include "../windows/shield_dialog.h" #include "../windows/request_payment_dialog.h" #include "../windows/block_info_dialog.h" #include "../windows/export_all_keys_dialog.h" #include "../windows/export_transactions_dialog.h" #include "imgui.h" #include #include #include #include #include namespace dragonx { namespace ui { using namespace material; // ============================================================================ // Settings state — loaded from config::Settings on first render // ============================================================================ static bool sp_initialized = false; static int sp_language_index = 0; static bool sp_save_ztxs = true; static bool sp_allow_custom_fees = false; static bool sp_auto_shield = false; static bool sp_fetch_prices = true; static bool sp_use_tor = false; static char sp_rpc_host[128] = DRAGONX_DEFAULT_RPC_HOST; static char sp_rpc_port[16] = DRAGONX_DEFAULT_RPC_PORT; static char sp_rpc_user[64] = ""; static char sp_rpc_password[64] = ""; static char sp_tx_explorer[256] = "https://explorer.dragonx.is/tx/"; static char sp_addr_explorer[256] = "https://explorer.dragonx.is/address/"; // Acrylic settings static bool sp_acrylic_enabled = true; static int sp_acrylic_quality = 2; static float sp_blur_multiplier = 1.0f; static bool sp_reduced_transparency = false; static void loadSettingsPageState(config::Settings* settings) { if (!settings) return; sp_save_ztxs = settings->getSaveZtxs(); sp_allow_custom_fees = settings->getAllowCustomFees(); sp_auto_shield = settings->getAutoShield(); sp_fetch_prices = settings->getFetchPrices(); sp_use_tor = settings->getUseTor(); strncpy(sp_tx_explorer, settings->getTxExplorerUrl().c_str(), sizeof(sp_tx_explorer) - 1); strncpy(sp_addr_explorer, settings->getAddressExplorerUrl().c_str(), sizeof(sp_addr_explorer) - 1); auto& i18n = util::I18n::instance(); const auto& languages = i18n.getAvailableLanguages(); std::string current_lang = settings->getLanguage(); if (current_lang.empty()) current_lang = "en"; sp_language_index = 0; int idx = 0; for (const auto& lang : languages) { if (lang.first == current_lang) { sp_language_index = idx; break; } idx++; } sp_acrylic_enabled = effects::ImGuiAcrylic::IsEnabled(); sp_acrylic_quality = static_cast(effects::ImGuiAcrylic::GetQuality()); sp_blur_multiplier = effects::ImGuiAcrylic::GetBlurMultiplier(); sp_reduced_transparency = effects::ImGuiAcrylic::GetReducedTransparency(); sp_initialized = true; } static void saveSettingsPageState(config::Settings* settings) { if (!settings) return; settings->setTheme(settings->getSkinId()); settings->setSaveZtxs(sp_save_ztxs); settings->setAllowCustomFees(sp_allow_custom_fees); settings->setAutoShield(sp_auto_shield); settings->setFetchPrices(sp_fetch_prices); settings->setUseTor(sp_use_tor); settings->setTxExplorerUrl(sp_tx_explorer); settings->setAddressExplorerUrl(sp_addr_explorer); auto& i18n = util::I18n::instance(); const auto& languages = i18n.getAvailableLanguages(); auto it = languages.begin(); std::advance(it, sp_language_index); if (it != languages.end()) { settings->setLanguage(it->first); } settings->save(); } // ============================================================================ // Settings Page Renderer // ============================================================================ void RenderSettingsPage(App* app) { // Load settings state on first render if (!sp_initialized && app->settings()) { loadSettingsPageState(app->settings()); } auto& S = schema::UI(); // Responsive layout — matches other tabs ImVec2 contentAvail = ImGui::GetContentRegionAvail(); float availWidth = contentAvail.x; float hs = Layout::hScale(availWidth); float vs = Layout::vScale(contentAvail.y); float pad = Layout::cardInnerPadding(); float gap = Layout::cardGap(); float glassRound = Layout::glassRounding(); (void)vs; char buf[256]; // Label column position — adaptive to width float labelW = std::max(100.0f, 120.0f * hs); // Input field width — fill remaining space in card float inputW = std::max(180.0f, availWidth - labelW - pad * 3); // Scrollable content area — NoBackground matches other tabs ImGui::BeginChild("##SettingsPageScroll", ImVec2(0, 0), false, ImGuiWindowFlags_NoBackground); // Get draw list AFTER BeginChild so we draw on the child window's list ImDrawList* dl = ImGui::GetWindowDrawList(); GlassPanelSpec glassSpec; glassSpec.rounding = glassRound; ImFont* ovFont = Type().overline(); ImFont* capFont = Type().caption(); ImFont* body2 = Type().body2(); ImFont* sub1 = Type().subtitle1(); // ==================================================================== // GENERAL — Appearance & Preferences card // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "APPEARANCE"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); // Measure content height for card // We'll use ImGui cursor-based layout inside the card ImVec2 cardMin = ImGui::GetCursorScreenPos(); // Use a child window inside the glass panel for layout // First draw the glass panel, then place content // We need to estimate height — use a generous estimate and clip float rowH = body2->LegacySize + Layout::spacingSm(); float sectionGap = Layout::spacingMd(); float cardH = pad // top pad + rowH // Theme + rowH // Language + sectionGap + rowH * 5 // Visual effects (acrylic + quality + blur + reduce + gap) + pad; // bottom pad if (!sp_acrylic_enabled) cardH -= rowH * 2; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); // --- Theme row --- { ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Theme"); ImGui::SameLine(labelW); auto& skinMgr = schema::SkinManager::instance(); const auto& skins = skinMgr.available(); std::string active_preview = "DragonX"; bool active_is_custom = false; for (const auto& skin : skins) { if (skin.id == skinMgr.activeSkinId()) { active_preview = skin.name; active_is_custom = !skin.bundled; break; } } float refreshBtnW = 80.0f; ImGui::SetNextItemWidth(inputW - refreshBtnW - Layout::spacingSm()); if (ImGui::BeginCombo("##Theme", active_preview.c_str())) { ImGui::TextDisabled("Built-in"); ImGui::Separator(); for (size_t i = 0; i < skins.size(); i++) { const auto& skin = skins[i]; if (!skin.bundled) continue; bool is_selected = (skin.id == skinMgr.activeSkinId()); if (ImGui::Selectable(skin.name.c_str(), is_selected)) { skinMgr.setActiveSkin(skin.id); if (app->settings()) { app->settings()->setSkinId(skin.id); app->settings()->save(); } } if (is_selected) ImGui::SetItemDefaultFocus(); } bool has_custom = false; for (const auto& skin : skins) { if (!skin.bundled) { has_custom = true; break; } } if (has_custom) { ImGui::Spacing(); ImGui::TextDisabled("Custom"); ImGui::Separator(); for (size_t i = 0; i < skins.size(); i++) { const auto& skin = skins[i]; if (skin.bundled) continue; bool is_selected = (skin.id == skinMgr.activeSkinId()); if (!skin.valid) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f)); ImGui::BeginDisabled(true); std::string lbl = skin.name + " (invalid)"; ImGui::Selectable(lbl.c_str(), false); ImGui::EndDisabled(); ImGui::PopStyleColor(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", skin.validationError.c_str()); } else { std::string lbl = skin.name; if (!skin.author.empty()) lbl += " (" + skin.author + ")"; if (ImGui::Selectable(lbl.c_str(), is_selected)) { skinMgr.setActiveSkin(skin.id); if (app->settings()) { app->settings()->setSkinId(skin.id); app->settings()->save(); } } if (is_selected) ImGui::SetItemDefaultFocus(); } } } ImGui::EndCombo(); } if (active_is_custom) { ImGui::SameLine(); ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "*"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Custom theme active"); } ImGui::SameLine(); if (TactileButton("Refresh", ImVec2(refreshBtnW, 0), S.resolveFont("button"))) { schema::SkinManager::instance().refresh(); Notifications::instance().info("Theme list refreshed"); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Scan for new themes.\nPlace theme folders in:\n%s", schema::SkinManager::getUserSkinsDirectory().c_str()); } ImGui::PopFont(); } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); // --- Language row --- { ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Language"); ImGui::SameLine(labelW); auto& i18n = util::I18n::instance(); const auto& languages = i18n.getAvailableLanguages(); std::vector lang_names; lang_names.reserve(languages.size()); for (const auto& lang : languages) { lang_names.push_back(lang.second.c_str()); } ImGui::SetNextItemWidth(inputW); if (ImGui::Combo("##Language", &sp_language_index, lang_names.data(), static_cast(lang_names.size()))) { auto it = languages.begin(); std::advance(it, sp_language_index); i18n.loadLanguage(it->first); } ImGui::PopFont(); } ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // --- Visual Effects subsection --- dl->AddText(ovFont, ovFont->LegacySize, ImGui::GetCursorScreenPos(), OnSurfaceMedium(), "VISUAL EFFECTS"); ImGui::Dummy(ImVec2(0, ovFont->LegacySize + Layout::spacingXs())); { // Two-column: left = acrylic toggle + reduce toggle, right = quality + blur float colW = (availWidth - pad * 2 - Layout::spacingLg()) * 0.5f; if (ImGui::Checkbox("Acrylic effects", &sp_acrylic_enabled)) { effects::ImGuiAcrylic::SetEnabled(sp_acrylic_enabled); } if (sp_acrylic_enabled) { ImGui::SameLine(labelW + colW + Layout::spacingLg()); ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Quality"); ImGui::PopFont(); ImGui::SameLine(); const char* quality_levels[] = { "Off", "Low", "Medium", "High" }; ImGui::SetNextItemWidth(std::max(100.0f, colW - 80.0f)); if (ImGui::Combo("##AcrylicQuality", &sp_acrylic_quality, quality_levels, IM_ARRAYSIZE(quality_levels))) { effects::ImGuiAcrylic::SetQuality( static_cast(sp_acrylic_quality)); } } if (ImGui::Checkbox("Reduce transparency", &sp_reduced_transparency)) { effects::ImGuiAcrylic::SetReducedTransparency(sp_reduced_transparency); } if (sp_acrylic_enabled) { ImGui::SameLine(labelW + colW + Layout::spacingLg()); ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Blur"); ImGui::PopFont(); ImGui::SameLine(); ImGui::SetNextItemWidth(std::max(100.0f, colW - 80.0f)); if (ImGui::SliderFloat("##BlurAmount", &sp_blur_multiplier, 0.5f, 2.0f, "%.1fx")) { effects::ImGuiAcrylic::SetBlurMultiplier(sp_blur_multiplier); } } } // Recalculate actual card bottom from cursor ImVec2 cardEnd = ImGui::GetCursorScreenPos(); float actualH = (cardEnd.y - cardMin.y) + pad; if (actualH != cardH) { // Redraw glass panel with correct height cardMax.y = cardMin.y + actualH; } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // PRIVACY & OPTIONS — Two cards side by side // ==================================================================== { float colW = (availWidth - gap) * 0.5f; ImVec2 rowOrigin = ImGui::GetCursorScreenPos(); // --- Privacy card (left) --- { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "PRIVACY"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float cardH = pad + (body2->LegacySize + Layout::spacingSm()) * 3 + pad; ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); ImGui::Checkbox("Save shielded tx history", &sp_save_ztxs); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::Checkbox("Auto-shield transparent funds", &sp_auto_shield); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::Checkbox("Use Tor for connections", &sp_use_tor); ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(colW, 0)); } // --- Options card (right) --- { float rightX = rowOrigin.x + colW + gap; // Position cursor at the same Y as privacy label ImGui::SetCursorScreenPos(ImVec2(rightX, rowOrigin.y)); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "OPTIONS"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float cardH = pad + (body2->LegacySize + Layout::spacingSm()) * 3 + pad; ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); ImGui::Checkbox("Allow custom transaction fees", &sp_allow_custom_fees); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::Checkbox("Fetch price data from CoinGecko", &sp_fetch_prices); ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(colW, 0)); } // Advance past the side-by-side row // Find the maximum bottom float rowBottom = ImGui::GetCursorScreenPos().y; ImGui::SetCursorScreenPos(ImVec2(rowOrigin.x, rowBottom)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // EXPLORER URLS + SAVE — card // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "BLOCK EXPLORER & SETTINGS"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float rowH = body2->LegacySize + Layout::spacingSm(); float cardH = pad + rowH * 2 + Layout::spacingSm() + body2->LegacySize + Layout::spacingMd() // save/reset row + pad; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); // Transaction URL { ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Transaction URL"); ImGui::SameLine(labelW); ImGui::SetNextItemWidth(inputW); ImGui::InputText("##TxExplorer", sp_tx_explorer, sizeof(sp_tx_explorer)); ImGui::PopFont(); } // Address URL { ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Address URL"); ImGui::SameLine(labelW); ImGui::SetNextItemWidth(inputW); ImGui::InputText("##AddrExplorer", sp_addr_explorer, sizeof(sp_addr_explorer)); ImGui::PopFont(); } ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // Save / Reset — right-aligned { float saveBtnW = 120.0f; float resetBtnW = 140.0f; float btnGap = Layout::spacingSm(); if (TactileButton("Save Settings", ImVec2(saveBtnW, 0), S.resolveFont("button"))) { saveSettingsPageState(app->settings()); Notifications::instance().success("Settings saved"); } ImGui::SameLine(0, btnGap); if (TactileButton("Reset to Defaults", ImVec2(resetBtnW, 0), S.resolveFont("button"))) { if (app->settings()) { loadSettingsPageState(app->settings()); Notifications::instance().info("Settings reloaded from disk"); } } } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // KEYS & BACKUP — card with two rows // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "KEYS & BACKUP"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm(); float cardH = pad + btnRowH * 2 + Layout::spacingSm() + pad; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); // Keys row — spread buttons across width { float btnW = (availWidth - pad * 2 - Layout::spacingSm() * 2) / 3.0f; if (TactileButton("Import Private Key...", ImVec2(btnW, 0), S.resolveFont("button"))) { app->showImportKeyDialog(); } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Export Private Key...", ImVec2(btnW, 0), S.resolveFont("button"))) { app->showExportKeyDialog(); } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Export All Keys...", ImVec2(btnW, 0), S.resolveFont("button"))) { ExportAllKeysDialog::show(); } } ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // Backup row { float btnW = (availWidth - pad * 2 - Layout::spacingSm()) / 2.0f; if (TactileButton("Backup wallet.dat...", ImVec2(btnW, 0), S.resolveFont("button"))) { app->showBackupDialog(); } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Export Transactions CSV...", ImVec2(btnW, 0), S.resolveFont("button"))) { ExportTransactionsDialog::show(); } } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // WALLET — Two cards side by side: Tools | Maintenance // ==================================================================== { float colW = (availWidth - gap) * 0.5f; ImVec2 rowOrigin = ImGui::GetCursorScreenPos(); float btnH = std::max(28.0f, 34.0f * vs); // --- Wallet Tools card (left) --- { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "WALLET TOOLS"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float cardH = pad + (btnH + Layout::spacingSm()) * 3 + pad; ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); float innerBtnW = colW - pad * 2; if (TactileButton("Address Book...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { AddressBookDialog::show(); } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); if (TactileButton("Validate Address...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { ValidateAddressDialog::show(); } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); if (TactileButton("Request Payment...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { RequestPaymentDialog::show(); } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(colW, 0)); } // --- Shielding & Maintenance card (right) --- { float rightX = rowOrigin.x + colW + gap; ImGui::SetCursorScreenPos(ImVec2(rightX, rowOrigin.y)); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "SHIELDING & MAINTENANCE"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float cardH = pad + (btnH + Layout::spacingSm()) * 3 + pad; ImVec2 cardMax(cardMin.x + colW, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); float innerBtnW = colW - pad * 2; if (TactileButton("Shield Mining Rewards...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase); } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); if (TactileButton("Merge to Address...", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { ShieldDialog::show(ShieldDialog::Mode::MergeToAddress); } ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::BeginDisabled(!app->isConnected()); if (TactileButton("Rescan Blockchain", ImVec2(innerBtnW, btnH), S.resolveFont("button"))) { if (app->rpc() && app->rpc()->isConnected()) { app->rpc()->rescanBlockchain(0, [](bool success, const nlohmann::json&) { if (success) Notifications::instance().success("Blockchain rescan started"); else Notifications::instance().error("Failed to start rescan"); }); } else { Notifications::instance().warning("Not connected to daemon"); } } ImGui::EndDisabled(); ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(colW, 0)); } // Advance past sidebar row float rowBottom = ImGui::GetCursorScreenPos().y; ImGui::SetCursorScreenPos(ImVec2(rowOrigin.x, rowBottom)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // WALLET INFO — Small card with file path + clear history // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "WALLET INFO"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float rowH = body2->LegacySize + Layout::spacingSm(); float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm(); float cardH = pad + rowH * 2 + btnRowH + pad; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); std::string wallet_path = util::Platform::getDragonXDataDir() + "wallet.dat"; uint64_t wallet_size = util::Platform::getFileSize(wallet_path); ImGui::PushFont(body2); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Location"); ImGui::SameLine(labelW); ImGui::TextUnformatted(wallet_path.c_str()); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("File size"); ImGui::SameLine(labelW); if (wallet_size > 0) { std::string size_str = util::Platform::formatFileSize(wallet_size); ImGui::TextUnformatted(size_str.c_str()); } else { ImGui::TextDisabled("Not found"); } ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); if (TactileButton("Clear Z-Transaction History", ImVec2(0, 0), S.resolveFont("button"))) { std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json"; if (util::Platform::deleteFile(ztx_file)) Notifications::instance().success("Z-transaction history cleared"); else Notifications::instance().info("No history file found"); } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // NODE / RPC — card with two-column inputs // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "NODE / RPC CONNECTION"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float rowH = body2->LegacySize + Layout::spacingSm(); float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm(); float cardH = pad + rowH * 2 + Layout::spacingSm() + rowH * 2 + Layout::spacingSm() + capFont->LegacySize + Layout::spacingSm() + btnRowH + pad; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); // Two-column: Host+Port on one line, User+Pass on next float halfInput = (availWidth - pad * 2 - labelW * 2 - Layout::spacingLg()) * 0.5f; float rpcLabelW = std::max(70.0f, 85.0f * hs); ImGui::PushFont(body2); // Row 1: Host + Port ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Host"); ImGui::SameLine(rpcLabelW); ImGui::SetNextItemWidth(halfInput + labelW - rpcLabelW); ImGui::InputText("##RPCHost", sp_rpc_host, sizeof(sp_rpc_host)); ImGui::SameLine(0, Layout::spacingLg()); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Port"); ImGui::SameLine(); ImGui::SetNextItemWidth(std::max(60.0f, halfInput * 0.4f)); ImGui::InputText("##RPCPort", sp_rpc_port, sizeof(sp_rpc_port)); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); // Row 2: Username + Password ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Username"); ImGui::SameLine(rpcLabelW); ImGui::SetNextItemWidth(halfInput + labelW - rpcLabelW); ImGui::InputText("##RPCUser", sp_rpc_user, sizeof(sp_rpc_user)); ImGui::SameLine(0, Layout::spacingLg()); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Password"); ImGui::SameLine(); ImGui::SetNextItemWidth(halfInput); ImGui::InputText("##RPCPassword", sp_rpc_password, sizeof(sp_rpc_password), ImGuiInputTextFlags_Password); ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "Connection settings are usually auto-detected from DRAGONX.conf"); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); if (TactileButton("Test Connection", ImVec2(0, 0), S.resolveFont("button"))) { if (app->rpc() && app->rpc()->isConnected()) { app->rpc()->getInfo([](const nlohmann::json& result, const std::string& error) { (void)result; if (error.empty()) Notifications::instance().success("RPC connection OK"); else Notifications::instance().error("RPC error: " + error); }); } else { Notifications::instance().warning("Not connected to daemon"); } } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Block Info...", ImVec2(0, 0), S.resolveFont("button"))) { BlockInfoDialog::show(app->getBlockHeight()); } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); // ==================================================================== // ABOUT — card // ==================================================================== { Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "ABOUT"); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImVec2 cardMin = ImGui::GetCursorScreenPos(); float rowH = body2->LegacySize + Layout::spacingXs(); float btnRowH = std::max(28.0f, 34.0f * vs) + Layout::spacingSm(); float cardH = pad + sub1->LegacySize + rowH * 2 + Layout::spacingSm() + body2->LegacySize * 2 + Layout::spacingSm() + capFont->LegacySize * 2 + Layout::spacingMd() + btnRowH + pad; ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH); DrawGlassPanel(dl, cardMin, cardMax, glassSpec); ImGui::SetCursorScreenPos(ImVec2(cardMin.x + pad, cardMin.y + pad)); // App name + version on same line ImGui::PushFont(sub1); ImGui::TextUnformatted(DRAGONX_APP_NAME); ImGui::PopFont(); ImGui::SameLine(0, Layout::spacingLg()); ImGui::PushFont(body2); snprintf(buf, sizeof(buf), "v%s", DRAGONX_VERSION); ImGui::TextUnformatted(buf); ImGui::SameLine(0, Layout::spacingLg()); snprintf(buf, sizeof(buf), "ImGui %s", IMGUI_VERSION); ImGui::TextColored(ImVec4(1,1,1,0.4f), "%s", buf); ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::PushFont(body2); ImGui::PushTextWrapPos(cardMax.x - pad); ImGui::TextUnformatted( "A shielded cryptocurrency wallet for DragonX (DRGX), " "built with Dear ImGui for a lightweight, portable experience."); ImGui::PopTextWrapPos(); ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::PushFont(capFont); ImGui::TextColored(ImVec4(1,1,1,0.5f), "Copyright 2024-2026 The Hush Developers | GPLv3 License"); ImGui::PopFont(); ImGui::Dummy(ImVec2(0, Layout::spacingMd())); // Buttons — spread across width { float btnW = (availWidth - pad * 2 - Layout::spacingSm() * 2) / 3.0f; if (TactileButton("Website", ImVec2(btnW, 0), S.resolveFont("button"))) { util::Platform::openUrl("https://dragonx.is"); } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Report Bug", ImVec2(btnW, 0), S.resolveFont("button"))) { util::Platform::openUrl("https://git.hush.is/hush/SilentDragonX/issues"); } ImGui::SameLine(0, Layout::spacingSm()); if (TactileButton("Block Explorer", ImVec2(btnW, 0), S.resolveFont("button"))) { util::Platform::openUrl("https://explorer.dragonx.is"); } } ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y)); ImGui::Dummy(ImVec2(availWidth, 0)); } ImGui::Dummy(ImVec2(0, gap)); ImGui::EndChild(); // ##SettingsPageScroll } } // namespace ui } // namespace dragonx