diff --git a/src/ui/windows/settings_window.cpp b/src/ui/windows/settings_window.cpp deleted file mode 100644 index d42a3c8..0000000 --- a/src/ui/windows/settings_window.cpp +++ /dev/null @@ -1,608 +0,0 @@ -// DragonX Wallet - ImGui Edition -// Copyright 2024-2026 The Hush Developers -// Released under the GPLv3 - -#include "settings_window.h" -#include "../../app.h" -#include "../../config/version.h" -#include "../../config/settings.h" -#include "../../util/i18n.h" -#include "../../util/platform.h" -#include "../../rpc/rpc_client.h" -#include "../theme.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 "../../embedded/IconsMaterialDesign.h" -#include "imgui.h" -#include -#include - -// Icon text for settings UI -#define ICON_CUSTOM_THEME ICON_MD_TUNE -#define ICON_REFRESH_THEMES ICON_MD_REFRESH - -namespace dragonx { -namespace ui { - -// Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID -static std::string TrId(const char* tr_key, const char* id) { - std::string s = TR(tr_key); - s += "##"; - s += id; - return s; -} - -// Settings state - these get loaded from Settings on window open -static bool s_initialized = false; -static int s_language_index = 0; -static bool s_save_ztxs = true; -static bool s_allow_custom_fees = false; -static bool s_auto_shield = false; -static bool s_fetch_prices = true; -static bool s_use_tor = false; -static char s_rpc_host[128] = DRAGONX_DEFAULT_RPC_HOST; -static char s_rpc_port[16] = DRAGONX_DEFAULT_RPC_PORT; -static char s_rpc_user[64] = ""; -static char s_rpc_password[64] = ""; -static char s_tx_explorer[256] = "https://explorer.dragonx.is/tx/"; -static char s_addr_explorer[256] = "https://explorer.dragonx.is/address/"; - -// Acrylic settings -static bool s_acrylic_enabled = true; -static float s_blur_amount = 1.5f; // 0.0=Off, continuous blur multiplier -static float s_noise_opacity = 0.5f; -static bool s_reduced_transparency = false; // Accessibility option -static bool s_gradient_background = false; // Gradient background mode - -// Saved skin ID for cancel/revert -static std::string s_saved_skin_id; - -// Load current settings into UI state -static void loadSettingsToUI(config::Settings* settings) { - if (!settings) return; - - s_saved_skin_id = settings->getSkinId(); - s_save_ztxs = settings->getSaveZtxs(); - s_allow_custom_fees = settings->getAllowCustomFees(); - s_auto_shield = settings->getAutoShield(); - s_fetch_prices = settings->getFetchPrices(); - s_use_tor = settings->getUseTor(); - - strncpy(s_tx_explorer, settings->getTxExplorerUrl().c_str(), sizeof(s_tx_explorer) - 1); - strncpy(s_addr_explorer, settings->getAddressExplorerUrl().c_str(), sizeof(s_addr_explorer) - 1); - - // Set language index - auto& i18n = util::I18n::instance(); - const auto& languages = i18n.getAvailableLanguages(); - std::string current_lang = settings->getLanguage(); - if (current_lang.empty()) current_lang = "en"; - - s_language_index = 0; - int idx = 0; - for (const auto& lang : languages) { - if (lang.first == current_lang) { - s_language_index = idx; - break; - } - idx++; - } - - s_initialized = true; -} - -// Save UI state to settings -static void saveSettingsFromUI(config::Settings* settings) { - if (!settings) return; - - settings->setTheme(settings->getSkinId()); // Theme now synced with skin - settings->setSaveZtxs(s_save_ztxs); - settings->setAllowCustomFees(s_allow_custom_fees); - settings->setAutoShield(s_auto_shield); - settings->setFetchPrices(s_fetch_prices); - settings->setUseTor(s_use_tor); - settings->setTxExplorerUrl(s_tx_explorer); - settings->setAddressExplorerUrl(s_addr_explorer); - - // Save language - auto& i18n = util::I18n::instance(); - const auto& languages = i18n.getAvailableLanguages(); - auto it = languages.begin(); - std::advance(it, s_language_index); - if (it != languages.end()) { - settings->setLanguage(it->first); - } - - // Save acrylic / visual effects settings - settings->setAcrylicEnabled(s_acrylic_enabled); - settings->setAcrylicQuality(s_blur_amount > 0.001f ? static_cast(effects::AcrylicQuality::Low) : static_cast(effects::AcrylicQuality::Off)); - settings->setBlurMultiplier(s_blur_amount); - settings->setReducedTransparency(s_reduced_transparency); - settings->setNoiseOpacity(s_noise_opacity); - settings->setGradientBackground(s_gradient_background); - - settings->save(); -} - -void RenderSettingsWindow(App* app, bool* p_open) -{ - // Load settings on first open - if (!s_initialized && app->settings()) { - loadSettingsToUI(app->settings()); - // Initialize acrylic settings from current state - s_acrylic_enabled = effects::ImGuiAcrylic::IsEnabled(); - s_blur_amount = effects::ImGuiAcrylic::GetBlurMultiplier(); - s_noise_opacity = effects::ImGuiAcrylic::GetNoiseOpacity(); - s_reduced_transparency = effects::ImGuiAcrylic::GetReducedTransparency(); - s_gradient_background = schema::SkinManager::instance().isGradientMode(); - } - - auto& S = schema::UI(); - auto win = S.window("dialogs.settings"); - auto lbl = S.label("dialogs.settings", "label"); - auto cmb = S.combo("dialogs.settings", "combo"); - auto connLbl = S.label("dialogs.settings", "connection-label"); - auto portInput = S.input("dialogs.settings", "port-input"); - auto walletBtn = S.button("dialogs.settings", "wallet-button"); - auto saveBtn = S.button("dialogs.settings", "save-button"); - auto cancelBtn = S.button("dialogs.settings", "cancel-button"); - - if (!material::BeginOverlayDialog(TR("settings"), p_open, win.width, 0.94f)) { - return; - } - - if (ImGui::BeginTabBar("SettingsTabs")) { - // General settings tab - if (ImGui::BeginTabItem(TR("general"))) { - ImGui::Spacing(); - - // Skin/theme selection - ImGui::Text("%s", TR("theme")); - ImGui::SameLine(lbl.position); - - // Active skin combo (populated from SkinManager) - auto& skinMgr = schema::SkinManager::instance(); - const auto& skins = skinMgr.available(); - - // Find active skin for preview text - 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; - } - } - - ImGui::SetNextItemWidth(cmb.width); - if (ImGui::BeginCombo("##Theme", active_preview.c_str())) { - // Bundled themes header - ImGui::TextDisabled("%s", TR("settings_builtin")); - 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(); - } - } - - // Custom themes (if any) - bool has_custom = false; - for (const auto& skin : skins) { - if (!skin.bundled) { has_custom = true; break; } - } - if (has_custom) { - ImGui::Spacing(); - ImGui::TextDisabled("%s", TR("settings_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 label = skin.name + TR("swin_invalid_suffix"); - ImGui::Selectable(label.c_str(), false); - ImGui::EndDisabled(); - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - material::Tooltip("%s", skin.validationError.c_str()); - } - } else { - std::string label = skin.name; - if (!skin.author.empty()) { - label += " (" + skin.author + ")"; - } - if (ImGui::Selectable(label.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 (ImGui::IsItemHovered()) - material::Tooltip("%s", TR("tt_theme_hotkey")); - - // Show indicator if custom theme is active - if (active_is_custom) { - ImGui::SameLine(); - ImGui::PushFont(material::Type().iconSmall()); - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_CUSTOM_THEME); - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("tt_custom_theme")); - } - } - - ImGui::SameLine(); - ImGui::PushFont(material::Type().iconSmall()); - if (material::StyledButton(ICON_REFRESH_THEMES, ImVec2(0, 0))) { - skinMgr.refresh(); - Notifications::instance().info(TR("swin_theme_list_refreshed")); - } - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - material::Tooltip(TR("tt_scan_themes"), - schema::SkinManager::getUserSkinsDirectory().c_str()); - } - - ImGui::Spacing(); - - // Language selection - ImGui::Text("%s", TR("language")); - ImGui::SameLine(lbl.position); - auto& i18n = util::I18n::instance(); - const auto& languages = i18n.getAvailableLanguages(); - - // Build language display names array - std::vector lang_names; - lang_names.reserve(languages.size()); - for (const auto& lang : languages) { - lang_names.push_back(lang.second.c_str()); // Display name - } - - ImGui::SetNextItemWidth(cmb.width); - if (ImGui::Combo("##Language", &s_language_index, lang_names.data(), static_cast(lang_names.size()))) { - // Get locale code from index - auto it = languages.begin(); - std::advance(it, s_language_index); - i18n.loadLanguage(it->first); - } - ImGui::TextDisabled(" %s", TR("settings_language_note")); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Acrylic Effects settings - ImGui::Text("%s", TR("settings_visual_effects")); - ImGui::Spacing(); - - ImGui::Text("%s", TR("settings_acrylic_level")); - ImGui::SameLine(lbl.position); - ImGui::SetNextItemWidth(cmb.width); - { - char blur_fmt[16]; - if (s_blur_amount < 0.01f) - snprintf(blur_fmt, sizeof(blur_fmt), "Off"); - else - snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", s_blur_amount * 25.0f); - if (ImGui::SliderFloat("##AcrylicBlur", &s_blur_amount, 0.0f, 4.0f, blur_fmt, - ImGuiSliderFlags_AlwaysClamp)) { - if (s_blur_amount > 0.0f && s_blur_amount < 0.15f) s_blur_amount = 0.0f; - s_acrylic_enabled = (s_blur_amount > 0.001f); - effects::ImGuiAcrylic::ApplyBlurAmount(s_blur_amount); - } - } - ImGui::TextDisabled(" %s", TR("tt_blur")); - - ImGui::Spacing(); - ImGui::Text("%s", TR("settings_noise_opacity")); - ImGui::SameLine(lbl.position); - ImGui::SetNextItemWidth(cmb.width); - { - char noise_fmt[16]; - if (s_noise_opacity < 0.01f) - snprintf(noise_fmt, sizeof(noise_fmt), "Off"); - else - snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", s_noise_opacity * 100.0f); - if (ImGui::SliderFloat("##NoiseOpacity", &s_noise_opacity, 0.0f, 1.0f, noise_fmt, - ImGuiSliderFlags_AlwaysClamp)) { - effects::ImGuiAcrylic::SetNoiseOpacity(s_noise_opacity); - } - } - ImGui::TextDisabled(" %s", TR("tt_noise")); - - ImGui::Spacing(); - - // Accessibility: Reduced transparency - if (ImGui::Checkbox(TrId("settings_reduce_transparency", "reduce_trans").c_str(), &s_reduced_transparency)) { - effects::ImGuiAcrylic::SetReducedTransparency(s_reduced_transparency); - } - ImGui::TextDisabled(" %s", TR("settings_solid_colors_desc")); - - ImGui::Spacing(); - - if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &s_gradient_background)) { - schema::SkinManager::instance().setGradientMode(s_gradient_background); - } - ImGui::TextDisabled(" %s", TR("settings_gradient_desc")); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Privacy settings - ImGui::Text("%s", TR("settings_privacy")); - ImGui::Spacing(); - - ImGui::Checkbox(TrId("settings_save_shielded_local", "save_ztx_w").c_str(), &s_save_ztxs); - ImGui::TextDisabled(" %s", TR("settings_save_shielded_desc")); - - ImGui::Spacing(); - - ImGui::Checkbox(TrId("settings_auto_shield_funds", "auto_shld_w").c_str(), &s_auto_shield); - ImGui::TextDisabled(" %s", TR("settings_auto_shield_desc")); - - ImGui::Spacing(); - - { - // Not implemented: disabled and never shown as "on", even for a stored use_tor=true. - bool torOff = false; - ImGui::BeginDisabled(); - ImGui::Checkbox(TrId("settings_use_tor_network", "tor_w").c_str(), &torOff); - ImGui::EndDisabled(); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - const float wrapW = S.drawElement("components.settings-page", "tor-tooltip-wrap-width") - .sizeOr(420.0f) * Layout::dpiScale(); - material::BeginTooltip(); - ImGui::PushTextWrapPos(wrapW); - ImGui::TextUnformatted(TR(app && app->isLiteBuild() ? "tt_tor_unavailable_lite" - : "tt_tor_unavailable")); - ImGui::PopTextWrapPos(); - material::EndTooltip(); - } - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Other settings - ImGui::Text("%s", TR("settings_other")); - ImGui::Spacing(); - - ImGui::Checkbox(TrId("custom_fees", "fees_w").c_str(), &s_allow_custom_fees); - ImGui::Checkbox(TrId("fetch_prices", "prices_w").c_str(), &s_fetch_prices); - - ImGui::EndTabItem(); - } - - // Connection settings tab - if (ImGui::BeginTabItem(TR("settings_connection"))) { - ImGui::Spacing(); - - ImGui::Text("%s", TR("settings_rpc_connection")); - ImGui::TextDisabled("%s", TR("settings_configure_rpc")); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - material::LabeledInputRow(TR("rpc_host"), connLbl.position, "##RPCHost", - s_rpc_host, sizeof(s_rpc_host), cmb.width); - - material::LabeledInputRow(TR("rpc_port"), connLbl.position, "##RPCPort", - s_rpc_port, sizeof(s_rpc_port), portInput.width); - - ImGui::Spacing(); - - material::LabeledInputRow(TR("rpc_user"), connLbl.position, "##RPCUser", - s_rpc_user, sizeof(s_rpc_user), cmb.width); - - material::LabeledInputRow(TR("rpc_pass"), connLbl.position, "##RPCPassword", - s_rpc_password, sizeof(s_rpc_password), cmb.width, nullptr, - ImGuiInputTextFlags_Password); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::TextDisabled("%s", TR("settings_rpc_note")); - - ImGui::Spacing(); - - if (material::StyledButton(TR("test_connection"), ImVec2(0,0), S.resolveFont("button"))) { - if (app->rpc()) { - rpc::RPCClient::TraceScope trace("Settings / Test connection"); - app->rpc()->getInfo([](const nlohmann::json& result, const std::string& error) { - if (error.empty()) { - std::string version = result.value("version", "unknown"); - std::string msg = std::string(TR("swin_connection_successful")) + version; - Notifications::instance().success(msg); - } else { - Notifications::instance().error(std::string(TR("swin_connection_failed")) + error); - } - }); - } else { - Notifications::instance().error(TR("swin_rpc_client_not_initialized")); - } - } - - ImGui::EndTabItem(); - } - - // Wallet tab - if (ImGui::BeginTabItem(TR("wallet"))) { - ImGui::Spacing(); - - ImGui::Text("%s", TR("settings_wallet_maintenance")); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - if (material::StyledButton(TR("rescan"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) { - if (app->rpc()) { - // Start rescan from block 0 - app->rpc()->rescanBlockchain(0, [](const nlohmann::json& result, const std::string& error) { - if (error.empty()) { - int start = result.value("start_height", 0); - int end = result.value("stop_height", 0); - std::string msg = std::string(TR("swin_rescan_started_from_block")) + std::to_string(start) + - TR("swin_rescan_to") + std::to_string(end); - Notifications::instance().success(msg); - } else { - Notifications::instance().error(std::string(TR("swin_rescan_failed")) + error); - } - }); - } else { - Notifications::instance().error(TR("swin_rpc_client_not_initialized")); - } - } - ImGui::TextDisabled(" %s", TR("settings_rescan_desc")); - - ImGui::Spacing(); - - static bool s_confirm_clear_ztx = false; - if (material::StyledButton(TR("settings_clear_ztx_long"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) { - s_confirm_clear_ztx = true; - } - ImGui::TextDisabled(" %s", TR("settings_clear_ztx_desc")); - - // Confirmation dialog - if (s_confirm_clear_ztx) { - // Distinct idSuffix: this confirm renders nested inside (and the same frame as) the parent - // settings dialog, so it must not share the default ##OverlayDialogContent key — otherwise it - // inherits the parent's OverlayCardState (incl. the sticky overflow flag) and collides on the - // child window id. - if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f, - 0.94f, 0.85f, "settings_clearztx")) { - material::DialogWarningHeader(TR("warning"), ImVec4(1.0f, 0.6f, 0.0f, 1.0f)); - - ImGui::Spacing(); - ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning1")); - ImGui::Spacing(); - ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning2")); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - bool doCancel = false, doConfirm = false; - material::DialogConfirmFooter(TR("cancel"), TrId("clear_anyway", "clear_ztx_w").c_str(), - true, doCancel, doConfirm); - if (doCancel) { - s_confirm_clear_ztx = false; - } - if (doConfirm) { - std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json"; - if (util::Platform::deleteFile(ztx_file)) { - Notifications::instance().success(TR("swin_ztx_history_cleared")); - } else { - Notifications::instance().info(TR("swin_no_history_file_found")); - } - s_confirm_clear_ztx = false; - } - material::EndOverlayDialog(); - } - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Text("%s", TR("settings_wallet_info")); - ImGui::Spacing(); - - // Get actual wallet size - std::string wallet_path = util::Platform::getDragonXDataDir() + "wallet.dat"; - uint64_t wallet_size = util::Platform::getFileSize(wallet_path); - if (wallet_size > 0) { - std::string size_str = util::Platform::formatFileSize(wallet_size); - ImGui::Text(TR("settings_wallet_file_size"), size_str.c_str()); - } else { - ImGui::TextDisabled("%s", TR("settings_wallet_not_found")); - } - ImGui::Text(TR("settings_wallet_location"), wallet_path.c_str()); - - ImGui::EndTabItem(); - } - - // Explorer tab - if (ImGui::BeginTabItem(TR("explorer"))) { - ImGui::Spacing(); - - ImGui::Text("%s", TR("settings_block_explorer_urls")); - ImGui::TextDisabled("%s", TR("settings_configure_explorer")); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Text("%s", TR("transaction_url")); - ImGui::SetNextItemWidth(-1); - ImGui::InputText("##TxExplorer", s_tx_explorer, sizeof(s_tx_explorer)); - - ImGui::Text("%s", TR("address_url")); - ImGui::SetNextItemWidth(-1); - ImGui::InputText("##AddrExplorer", s_addr_explorer, sizeof(s_addr_explorer)); - - ImGui::Spacing(); - ImGui::TextDisabled("%s", TR("settings_explorer_hint")); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Save/Cancel buttons - if (material::StyledButton(TR("save"), ImVec2(saveBtn.width, 0), S.resolveFont(saveBtn.font))) { - saveSettingsFromUI(app->settings()); - Notifications::instance().success(TR("swin_settings_saved")); - *p_open = false; - } - ImGui::SameLine(); - if (material::StyledButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) { - // Reload settings to revert changes - loadSettingsToUI(app->settings()); - // Revert skin to what was active when settings opened - if (!s_saved_skin_id.empty()) { - schema::SkinManager::instance().setActiveSkin(s_saved_skin_id); - if (app->settings()) { - app->settings()->setSkinId(s_saved_skin_id); - app->settings()->save(); - } - } - *p_open = false; - } - - material::EndOverlayDialog(); -} - -} // namespace ui -} // namespace dragonx diff --git a/src/ui/windows/settings_window.h b/src/ui/windows/settings_window.h deleted file mode 100644 index dbc3c2c..0000000 --- a/src/ui/windows/settings_window.h +++ /dev/null @@ -1,19 +0,0 @@ -// DragonX Wallet - ImGui Edition -// Copyright 2024-2026 The Hush Developers -// Released under the GPLv3 - -#pragma once - -namespace dragonx { -class App; - -namespace ui { - -/** - * @brief Render the Settings window - * Modal dialog for application settings - */ -void RenderSettingsWindow(App* app, bool* p_open); - -} // namespace ui -} // namespace dragonx