Files
ObsidianDragon/src/ui/pages/settings_page.cpp
T
DanS 40dd6d45b2 daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup
- Add startup binary version checking for dragonxd/xmrig
- Display daemon version in UI
- Add idle mining thread count adjustment
- Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard
- Add paste button to import private key dialog with address validation
- Add z-address generation UI feedback (loading indicator)
- Add option to delete blockchain data while preserving wallet.dat
- Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus)
- Fix Windows RPC auth: trim \r from config values, add .cookie fallback
- Fix connection status message during block index loading
- Improve application shutdown to prevent lingering background process
2026-03-17 14:57:12 -05:00

2082 lines
101 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "settings_page.h"
#include "../../app.h"
#include "../../config/version.h"
#include "../../config/settings.h"
#include "../../util/logger.h"
#include "../windows/balance_tab.h"
#include "../windows/console_tab.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../rpc/rpc_client.h"
#include "../../rpc/rpc_worker.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 "../effects/theme_effects.h"
#include "../effects/low_spec.h"
#include "../effects/scroll_fade_shader.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 "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include <nlohmann/json.hpp>
#include <vector>
#include <set>
#include <filesystem>
#include <algorithm>
#include <cmath>
namespace dragonx {
namespace ui {
using namespace material;
// 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 — 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 float sp_blur_amount = 1.5f; // 0.0=Off, 0.01–4.0 continuous blur multiplier
static float sp_noise_opacity = 0.5f; // 0.0–1.0 multiplier
static float sp_ui_opacity = 1.0f; // 0.3–1.0 card/sidebar opacity
static float sp_window_opacity = 1.0f; // 0.3–1.0 background alpha
// Balance layout (string ID)
static std::string sp_balance_layout = "classic";
// Console scanline
static bool sp_scanline_enabled = true;
// Theme effects
static bool sp_theme_effects_enabled = true;
// Gradient background mode
static bool sp_gradient_background = false;
// Low-spec mode
static bool sp_low_spec_mode = false;
// Font scale (user accessibility, 1.0–3.0)
static float sp_font_scale = 1.0f;
// Snapshot of effect settings saved when low-spec is toggled ON,
// restored when toggled OFF so user state isn't lost.
struct LowSpecSnapshot {
bool valid = false;
bool acrylic_enabled;
float blur_amount;
float ui_opacity;
float window_opacity;
bool theme_effects_enabled;
bool scanline_enabled;
};
static LowSpecSnapshot s_lowSpecSnap;
// Daemon — keep running on close
static bool sp_keep_daemon_running = false;
static bool sp_stop_external_daemon = false;
// Mining — mine when idle
static bool sp_mine_when_idle = false;
static int sp_mine_idle_delay = 120;
static bool sp_idle_thread_scaling = false;
static int sp_idle_threads_active = 0;
static int sp_idle_threads_idle = 0;
static bool sp_verbose_logging = false;
// Debug logging categories
static std::set<std::string> sp_debug_categories;
static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted
static bool sp_debug_expanded = false; // collapsible card state
static bool sp_effects_expanded = false; // "Advanced Effects..." toggle
static bool sp_tools_expanded = false; // "Tools & Actions..." toggle
static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history
static bool sp_confirm_delete_blockchain = false; // confirmation dialog for deleting blockchain data
// (APPEARANCE card now uses ChannelsSplit like all other cards)
// Shader-based scroll-edge fade (per-pixel alpha mask via custom fragment shader)
static effects::ScrollFadeShader s_fadeShader;
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++;
}
// Load blur amount directly from saved multiplier
sp_blur_amount = settings->getBlurMultiplier();
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
sp_ui_opacity = settings->getUIOpacity();
sp_window_opacity = settings->getWindowOpacity();
sp_noise_opacity = settings->getNoiseOpacity();
sp_gradient_background = settings->getGradientBackground();
sp_balance_layout = settings->getBalanceLayout();
sp_scanline_enabled = settings->getScanlineEnabled();
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
sp_theme_effects_enabled = settings->getThemeEffectsEnabled();
sp_low_spec_mode = settings->getLowSpecMode();
effects::setLowSpecMode(sp_low_spec_mode);
sp_font_scale = settings->getFontScale();
Layout::setUserFontScale(sp_font_scale); // sync with Layout on load
sp_keep_daemon_running = settings->getKeepDaemonRunning();
sp_stop_external_daemon = settings->getStopExternalDaemon();
sp_mine_when_idle = settings->getMineWhenIdle();
sp_mine_idle_delay = settings->getMineIdleDelay();
sp_idle_thread_scaling = settings->getIdleThreadScaling();
sp_idle_threads_active = settings->getIdleThreadsActive();
sp_idle_threads_idle = settings->getIdleThreadsIdle();
sp_verbose_logging = settings->getVerboseLogging();
sp_debug_categories = settings->getDebugCategories();
sp_debug_cats_dirty = false;
// Apply loaded visual effects settings
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
effects::ImGuiAcrylic::SetUIOpacity(sp_ui_opacity);
effects::ImGuiAcrylic::SetNoiseOpacity(sp_noise_opacity);
effects::ThemeEffects::instance().setEnabled(sp_theme_effects_enabled);
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);
}
// Visual effects settings
settings->setAcrylicEnabled(sp_acrylic_enabled);
settings->setAcrylicQuality(sp_blur_amount > 0.001f ? static_cast<int>(effects::AcrylicQuality::Low) : static_cast<int>(effects::AcrylicQuality::Off));
settings->setBlurMultiplier(sp_blur_amount);
settings->setUIOpacity(sp_ui_opacity);
settings->setWindowOpacity(sp_window_opacity);
settings->setNoiseOpacity(sp_noise_opacity);
settings->setGradientBackground(sp_gradient_background);
settings->setScanlineEnabled(sp_scanline_enabled);
settings->setThemeEffectsEnabled(sp_theme_effects_enabled);
settings->setLowSpecMode(sp_low_spec_mode);
settings->setFontScale(sp_font_scale);
settings->setKeepDaemonRunning(sp_keep_daemon_running);
settings->setStopExternalDaemon(sp_stop_external_daemon);
settings->setMineWhenIdle(sp_mine_when_idle);
settings->setMineIdleDelay(sp_mine_idle_delay);
settings->setIdleThreadScaling(sp_idle_thread_scaling);
settings->setIdleThreadsActive(sp_idle_threads_active);
settings->setIdleThreadsIdle(sp_idle_threads_idle);
settings->setVerboseLogging(sp_verbose_logging);
settings->setDebugCategories(sp_debug_categories);
settings->save();
}
// ============================================================================
// Settings Page Renderer
// ============================================================================
void RenderSettingsPage(App* app) {
// Load settings state on first render
if (!sp_initialized && app->settings()) {
loadSettingsPageState(app->settings());
}
// Sync low-spec / theme-effects state from runtime each frame
// so that hotkey toggles are reflected in the checkboxes.
{
bool runtimeLowSpec = effects::isLowSpecMode();
if (sp_low_spec_mode != runtimeLowSpec) {
if (runtimeLowSpec) {
// Hotkey turned low-spec ON — save snapshot, override statics
s_lowSpecSnap.valid = true;
s_lowSpecSnap.acrylic_enabled = sp_acrylic_enabled;
s_lowSpecSnap.blur_amount = sp_blur_amount;
s_lowSpecSnap.ui_opacity = sp_ui_opacity;
s_lowSpecSnap.window_opacity = sp_window_opacity;
s_lowSpecSnap.theme_effects_enabled = sp_theme_effects_enabled;
s_lowSpecSnap.scanline_enabled = sp_scanline_enabled;
sp_acrylic_enabled = false;
sp_blur_amount = 0.0f;
sp_ui_opacity = 1.0f;
sp_window_opacity = 1.0f;
sp_theme_effects_enabled = false;
sp_scanline_enabled = false;
} else if (s_lowSpecSnap.valid) {
// Hotkey turned low-spec OFF — restore snapshot
sp_blur_amount = s_lowSpecSnap.blur_amount;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
sp_ui_opacity = s_lowSpecSnap.ui_opacity;
sp_window_opacity = s_lowSpecSnap.window_opacity;
sp_theme_effects_enabled = s_lowSpecSnap.theme_effects_enabled;
sp_scanline_enabled = s_lowSpecSnap.scanline_enabled;
s_lowSpecSnap.valid = false;
} else if (app->settings()) {
// No snapshot — read prefs from settings file
sp_blur_amount = app->settings()->getBlurMultiplier();
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
sp_ui_opacity = app->settings()->getUIOpacity();
sp_window_opacity = app->settings()->getWindowOpacity();
sp_theme_effects_enabled = app->settings()->getThemeEffectsEnabled();
sp_scanline_enabled = app->settings()->getScanlineEnabled();
}
sp_low_spec_mode = runtimeLowSpec;
}
bool runtimeThemeEffects = effects::ThemeEffects::instance().isEnabled();
if (sp_theme_effects_enabled != runtimeThemeEffects) {
sp_theme_effects_enabled = runtimeThemeEffects;
}
}
auto& S = schema::UI();
// Responsive layout — matches other tabs
ImVec2 contentAvail = ImGui::GetContentRegionAvail();
float scrollbarMargin = ImGui::GetStyle().ScrollbarSize + Layout::spacingSm();
float availWidth = contentAvail.x - scrollbarMargin;
float hs = Layout::hScale(availWidth);
float vs = Layout::vScale(contentAvail.y);
float pad = Layout::cardInnerPadding();
float bottomPad = std::max(0.0f, pad - ImGui::GetStyle().ItemSpacing.y);
float gap = Layout::cardGap();
float glassRound = Layout::glassRounding();
char buf[256];
// Label column position — adaptive to width
float labelW = std::max(S.drawElement("components.settings-page", "label-min-width").size, S.drawElement("components.settings-page", "label-width").size * hs);
// Widen labelW if any translated label is wider (prevents overflow)
{
ImFont* mf = Type().body2();
const char* labelKeys[] = {"theme", "balance_layout", "language", "acrylic", "noise",
"ui_opacity", "window_opacity", "font_scale",
"rpc_host", "rpc_port", "rpc_user", "rpc_pass",
"transaction_url", "address_url"};
for (const char* k : labelKeys) {
float tw = mf->CalcTextSizeA(mf->LegacySize, FLT_MAX, 0, TR(k)).x + Layout::spacingMd();
if (tw > labelW) labelW = tw;
}
}
// Input field width — fill remaining space in card
float inputW = std::max(S.drawElement("components.settings-page", "input-min-width").size, availWidth - labelW - pad * 2);
// Scrollable content area — NoBackground matches other tabs
ImGui::BeginChild("##SettingsPageScroll", ImVec2(0, 0), false,
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
ApplySmoothScroll();
// Capture the ACTUAL clip boundaries from inside the child window
ImVec2 childClipMin = ImGui::GetWindowPos();
ImVec2 childClipMax(childClipMin.x + ImGui::GetWindowSize().x,
childClipMin.y + ImGui::GetWindowSize().y);
const float dp = Layout::dpiScale();
const float fadeH = schema::UI().drawElement("components.settings-page", "edge-fade-zone").size * dp;
const float fadeOffTop = schema::UI().drawElement("components.settings-page", "edge-fade-offset-top").size * dp;
const float fadeOffBot = schema::UI().drawElement("components.settings-page", "edge-fade-offset-bottom").size * dp;
// Get draw list AFTER BeginChild so we draw on the child window's list
ImDrawList* dl = ImGui::GetWindowDrawList();
(void)dl; // used by cards below
// Capture ForegroundDrawList vertex start — DrawGlassPanel draws
// theme effects (rainbow border, shimmer, specular glare, edge trace)
// on the ForegroundDrawList; those bypass the shader fade so we'll
// apply a vertex-based alpha fade to them after rendering.
ImDrawList* fgDL = ImGui::GetForegroundDrawList();
int fgVtxStart = fgDL->VtxBuffer.Size;
// --- Shader-based scroll fade: bind custom fragment shader ---
// The shader multiplies output alpha by a smoothstep gradient based
// on screen Y, giving a true per-pixel alpha mask at scroll edges.
float settingsScrollY_pre = ImGui::GetScrollY();
float settingsScrollMaxY_pre = ImGui::GetScrollMaxY();
float settingsFadeTopY = childClipMin.y + fadeOffTop;
float settingsFadeBottomY = childClipMax.y - fadeOffBot;
float settingsFadeZoneTop = (settingsScrollY_pre > 1.0f) ? fadeH : 0.0f;
float settingsFadeZoneBot = (settingsScrollMaxY_pre > 0 && settingsScrollY_pre < settingsScrollMaxY_pre - 1.0f) ? fadeH : 0.0f;
if (fadeH > 0.0f && !sp_low_spec_mode && s_fadeShader.init()) {
s_fadeShader.fadeTopY = settingsFadeTopY;
s_fadeShader.fadeBottomY = settingsFadeBottomY;
s_fadeShader.fadeZoneTop = settingsFadeZoneTop;
s_fadeShader.fadeZoneBottom = settingsFadeZoneBot;
s_fadeShader.addBind(dl);
}
// Top margin from schema
float topMargin = schema::UI().drawElement("components.settings-page", "top-margin").size;
if (topMargin > 0.0f)
ImGui::Dummy(ImVec2(0, topMargin));
GlassPanelSpec glassSpec;
glassSpec.rounding = glassRound;
ImFont* capFont = Type().caption();
ImFont* body2 = Type().body2();
ImFont* sub1 = Type().subtitle1();
// ====================================================================
// THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit
// which breaks BeginCombo popup rendering in some ImGui versions)
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
float comboGap = S.drawElement("components.settings-page", "combo-row-gap").size;
float compactBP = S.drawElement("components.settings-page", "compact-breakpoint").size;
bool wideLayout = availWidth >= compactBP;
float refreshBtnW = S.drawElement("components.settings-page", "refresh-btn-width").size;
// --- Skin data ---
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;
}
}
// --- Language data ---
auto& i18n = util::I18n::instance();
const auto& languages = i18n.getAvailableLanguages();
std::vector<const char*> lang_names;
lang_names.reserve(languages.size());
for (const auto& lang : languages) {
lang_names.push_back(lang.second.c_str());
}
// --- Balance layout data ---
const auto& layouts = GetBalanceLayouts();
std::string balPreview = sp_balance_layout;
for (const auto& l : layouts) {
if (l.id == sp_balance_layout) { balPreview = l.name; break; }
}
// --- Theme combo popup (shared between wide and narrow paths) ---
auto renderThemeComboPopup = [&]() {
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();
}
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 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();
}
}
}
};
if (wideLayout) {
// ============================================================
// Wide: 3 combos on one row + compact 3-column effects grid
// ============================================================
// --- Combo row: Theme | Layout | Language [Refresh] ---
{
ImGui::PushFont(body2);
float lblGap = Layout::spacingXs();
float lblThemeW = ImGui::CalcTextSize(TR("theme")).x + lblGap;
float lblLayoutW = ImGui::CalcTextSize(TR("balance_layout")).x + lblGap;
float lblLangW = ImGui::CalcTextSize(TR("language")).x + lblGap;
float totalFixed = lblThemeW + lblLayoutW + lblLangW
+ comboGap * 2 + Layout::spacingSm() + refreshBtnW;
float comboW = std::max(80.0f, (contentW - totalFixed) / 3.0f);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("theme"));
ImGui::SameLine(0, lblGap);
ImGui::SetNextItemWidth(comboW);
if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
renderThemeComboPopup();
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_theme_hotkey"));
ImGui::SameLine(0, comboGap);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("balance_layout"));
ImGui::SameLine(0, lblGap);
ImGui::SetNextItemWidth(comboW);
if (ImGui::BeginCombo("##BalanceLayout", balPreview.c_str())) {
for (const auto& l : layouts) {
if (!l.enabled) continue;
bool selected = (l.id == sp_balance_layout);
if (ImGui::Selectable(l.name.c_str(), selected)) {
sp_balance_layout = l.id;
if (app->settings()) {
app->settings()->setBalanceLayout(sp_balance_layout);
app->settings()->save();
}
}
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_layout_hotkey"));
ImGui::SameLine(0, comboGap);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("language"));
ImGui::SameLine(0, lblGap);
ImGui::SetNextItemWidth(comboW);
if (ImGui::Combo("##Language", &sp_language_index, lang_names.data(),
static_cast<int>(lang_names.size()))) {
auto it = languages.begin();
std::advance(it, sp_language_index);
i18n.loadLanguage(it->first);
if (app->settings()) {
app->settings()->setLanguage(it->first);
app->settings()->save();
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_language"));
ImGui::SameLine(0, Layout::spacingSm());
if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
schema::SkinManager::instance().refresh();
Notifications::instance().info("Theme list refreshed");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(TR("tt_scan_themes"),
schema::SkinManager::getUserSkinsDirectory().c_str());
}
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) ---
{
ImGui::PushFont(body2);
ImGui::TextUnformatted(TR("font_scale"));
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW);
ImGui::SetNextItemWidth(fontSliderW);
sp_font_scale = Layout::userFontScale();
float prev_font_scale = sp_font_scale;
{
char fs_fmt[16];
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
ImGuiSliderFlags_AlwaysClamp);
}
sp_font_scale = std::max(1.0f, std::min(1.5f,
std::round(sp_font_scale * 20.0f) / 20.0f));
if (sp_font_scale != prev_font_scale)
Layout::setUserFontScaleVisual(sp_font_scale);
if (ImGui::IsItemDeactivatedAfterEdit()) {
Layout::setUserFontScale(sp_font_scale);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Advanced Effects... ---
{
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##EffectsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
sp_effects_expanded = !sp_effects_expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_effects_expanded) {
ImGui::PushFont(body2);
// Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
effects::setLowSpecMode(sp_low_spec_mode);
if (sp_low_spec_mode) {
s_lowSpecSnap.valid = true;
s_lowSpecSnap.acrylic_enabled = sp_acrylic_enabled;
s_lowSpecSnap.blur_amount = sp_blur_amount;
s_lowSpecSnap.ui_opacity = sp_ui_opacity;
s_lowSpecSnap.window_opacity = sp_window_opacity;
s_lowSpecSnap.theme_effects_enabled = sp_theme_effects_enabled;
s_lowSpecSnap.scanline_enabled = sp_scanline_enabled;
sp_acrylic_enabled = false;
sp_blur_amount = 0.0f;
sp_ui_opacity = 1.0f;
sp_window_opacity = 1.0f;
sp_theme_effects_enabled = false;
sp_scanline_enabled = false;
effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
effects::ImGuiAcrylic::SetUIOpacity(1.0f);
effects::ThemeEffects::instance().setEnabled(false);
ConsoleTab::s_scanline_enabled = false;
} else if (s_lowSpecSnap.valid) {
sp_blur_amount = s_lowSpecSnap.blur_amount;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
sp_ui_opacity = s_lowSpecSnap.ui_opacity;
sp_window_opacity = s_lowSpecSnap.window_opacity;
sp_theme_effects_enabled = s_lowSpecSnap.theme_effects_enabled;
sp_scanline_enabled = s_lowSpecSnap.scanline_enabled;
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
effects::ImGuiAcrylic::SetUIOpacity(sp_ui_opacity);
effects::ThemeEffects::instance().setEnabled(sp_theme_effects_enabled);
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
s_lowSpecSnap.valid = false;
}
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &sp_gradient_background)) {
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_simple_bg"));
ImGui::BeginDisabled(sp_low_spec_mode);
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
app->settings()->setScanlineEnabled(sp_scanline_enabled);
app->settings()->save();
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_scanline"));
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("theme_effects", "effects").c_str(), &sp_theme_effects_enabled)) {
effects::ThemeEffects::instance().setEnabled(sp_theme_effects_enabled);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects"));
// Row 1: Acrylic preset slider + Noise slider (side by side, labels above)
float effCtrlMinW = S.drawElement("components.settings-page", "effects-input-min-width").size;
float halfW = (contentW - Layout::spacingLg()) * 0.5f;
float ctrlW = std::max(effCtrlMinW, halfW);
float baseX = ImGui::GetCursorScreenPos().x;
float rightX = baseX + ctrlW + Layout::spacingLg();
ImGui::TextUnformatted(TR("acrylic"));
float row1Y = ImGui::GetCursorScreenPos().y;
ImGui::SetNextItemWidth(ctrlW);
{
char blur_fmt[16];
if (sp_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
else
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
}
}
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
float afterRow1Y = ImGui::GetCursorScreenPos().y;
float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y;
ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH));
ImGui::TextUnformatted(TR("noise"));
ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y));
ImGui::SetNextItemWidth(ctrlW);
{
char noise_fmt[16];
if (sp_noise_opacity < 0.01f)
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
else
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
effects::ImGuiAcrylic::SetNoiseOpacity(sp_noise_opacity);
saveSettingsPageState(app->settings());
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y));
ImGui::TextUnformatted(TR("ui_opacity"));
float row2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetNextItemWidth(ctrlW);
{
char uiop_fmt[16];
snprintf(uiop_fmt, sizeof(uiop_fmt), "%.0f%%%%", sp_ui_opacity * 100.0f);
if (ImGui::SliderFloat("##UIOpacity", &sp_ui_opacity, 0.3f, 1.0f, uiop_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
effects::ImGuiAcrylic::SetUIOpacity(sp_ui_opacity);
saveSettingsPageState(app->settings());
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
float afterRow2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH));
ImGui::TextUnformatted(TR("window_opacity"));
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y));
ImGui::SetNextItemWidth(ctrlW);
{
char winop_fmt[16];
snprintf(winop_fmt, sizeof(winop_fmt), "%.0f%%%%", sp_window_opacity * 100.0f);
if (ImGui::SliderFloat("##WindowOpacity", &sp_window_opacity, 0.3f, 1.0f, winop_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
saveSettingsPageState(app->settings());
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity"));
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y));
ImGui::EndDisabled(); // low-spec
ImGui::PopFont();
} // sp_effects_expanded
} else {
// ============================================================
// Narrow: stacked combos + 2-column effects (original layout)
// ============================================================
// --- Theme row ---
{
ImGui::PushFont(body2);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("theme"));
ImGui::SameLine(labelW);
float themeComboW = std::max(S.drawElement("components.settings-page", "theme-combo-min-width").size,
availWidth - pad * 2 - labelW - refreshBtnW - Layout::spacingSm());
ImGui::SetNextItemWidth(themeComboW);
if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
renderThemeComboPopup();
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_theme_hotkey"));
if (active_is_custom) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "*");
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_custom_theme"));
}
ImGui::SameLine();
if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
schema::SkinManager::instance().refresh();
Notifications::instance().info("Theme list refreshed");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(TR("tt_scan_themes"),
schema::SkinManager::getUserSkinsDirectory().c_str());
}
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// --- Balance Layout row ---
{
ImGui::PushFont(body2);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("balance_layout"));
ImGui::SameLine(labelW);
ImGui::SetNextItemWidth(std::max(180.0f, inputW));
if (ImGui::BeginCombo("##BalanceLayout", balPreview.c_str())) {
for (const auto& l : layouts) {
if (!l.enabled) continue;
bool selected = (l.id == sp_balance_layout);
if (ImGui::Selectable(l.name.c_str(), selected)) {
sp_balance_layout = l.id;
if (app->settings()) {
app->settings()->setBalanceLayout(sp_balance_layout);
app->settings()->save();
}
}
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_layout_hotkey"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// --- Language row ---
{
ImGui::PushFont(body2);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("language"));
ImGui::SameLine(labelW);
ImGui::SetNextItemWidth(inputW);
if (ImGui::Combo("##Language", &sp_language_index, lang_names.data(),
static_cast<int>(lang_names.size()))) {
auto it = languages.begin();
std::advance(it, sp_language_index);
i18n.loadLanguage(it->first);
if (app->settings()) {
app->settings()->setLanguage(it->first);
app->settings()->save();
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_language"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Font Scale slider (always visible) ---
{
ImGui::PushFont(body2);
ImGui::TextUnformatted(TR("font_scale"));
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
availWidth - pad * 2);
ImGui::SetNextItemWidth(fontSliderW);
sp_font_scale = Layout::userFontScale();
float prev_font_scale = sp_font_scale;
{
char fs_fmt[16];
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
ImGuiSliderFlags_AlwaysClamp);
}
sp_font_scale = std::max(1.0f, std::min(1.5f,
std::round(sp_font_scale * 20.0f) / 20.0f));
if (sp_font_scale != prev_font_scale)
Layout::setUserFontScaleVisual(sp_font_scale);
if (ImGui::IsItemDeactivatedAfterEdit()) {
Layout::setUserFontScale(sp_font_scale);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
ImGui::PopFont();
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Advanced Effects... ---
{
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
float narrowContentW = availWidth - pad * 2;
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##EffectsToggleN", ImVec2(narrowContentW, ImGui::GetFrameHeight()))) {
sp_effects_expanded = !sp_effects_expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + narrowContentW - arrowW, textY), OnSurfaceMedium(), arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_effects_expanded) {
ImGui::PushFont(body2);
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
effects::setLowSpecMode(sp_low_spec_mode);
if (sp_low_spec_mode) {
s_lowSpecSnap.valid = true;
s_lowSpecSnap.acrylic_enabled = sp_acrylic_enabled;
s_lowSpecSnap.blur_amount = sp_blur_amount;
s_lowSpecSnap.ui_opacity = sp_ui_opacity;
s_lowSpecSnap.window_opacity = sp_window_opacity;
s_lowSpecSnap.theme_effects_enabled = sp_theme_effects_enabled;
s_lowSpecSnap.scanline_enabled = sp_scanline_enabled;
sp_acrylic_enabled = false;
sp_blur_amount = 0.0f;
sp_ui_opacity = 1.0f;
sp_window_opacity = 1.0f;
sp_theme_effects_enabled = false;
sp_scanline_enabled = false;
effects::ImGuiAcrylic::ApplyBlurAmount(0.0f);
effects::ImGuiAcrylic::SetUIOpacity(1.0f);
effects::ThemeEffects::instance().setEnabled(false);
ConsoleTab::s_scanline_enabled = false;
} else if (s_lowSpecSnap.valid) {
sp_blur_amount = s_lowSpecSnap.blur_amount;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
sp_ui_opacity = s_lowSpecSnap.ui_opacity;
sp_window_opacity = s_lowSpecSnap.window_opacity;
sp_theme_effects_enabled = s_lowSpecSnap.theme_effects_enabled;
sp_scanline_enabled = s_lowSpecSnap.scanline_enabled;
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
effects::ImGuiAcrylic::SetUIOpacity(sp_ui_opacity);
effects::ThemeEffects::instance().setEnabled(sp_theme_effects_enabled);
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
s_lowSpecSnap.valid = false;
}
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &sp_gradient_background)) {
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_simple_bg_alt"));
ImGui::BeginDisabled(sp_low_spec_mode);
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
app->settings()->setScanlineEnabled(sp_scanline_enabled);
app->settings()->save();
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_scanline"));
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("theme_effects", "theme_fx").c_str(), &sp_theme_effects_enabled)) {
effects::ThemeEffects::instance().setEnabled(sp_theme_effects_enabled);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects"));
float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
availWidth - pad * 2.0f);
ImGui::TextUnformatted(TR("acrylic"));
ImGui::SetNextItemWidth(ctrlW);
{
char blur_fmt[16];
if (sp_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
else
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f;
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
}
}
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
ImGui::TextUnformatted(TR("noise"));
ImGui::SetNextItemWidth(ctrlW);
{
char noise_fmt[16];
if (sp_noise_opacity < 0.01f)
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
else
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
effects::ImGuiAcrylic::SetNoiseOpacity(sp_noise_opacity);
}
}
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
ImGui::TextUnformatted(TR("ui_opacity"));
ImGui::SetNextItemWidth(ctrlW);
{
char uiop_fmt[16];
snprintf(uiop_fmt, sizeof(uiop_fmt), "%.0f%%%%", sp_ui_opacity * 100.0f);
if (ImGui::SliderFloat("##UIOpacity", &sp_ui_opacity, 0.3f, 1.0f, uiop_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
effects::ImGuiAcrylic::SetUIOpacity(sp_ui_opacity);
}
}
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
ImGui::TextUnformatted(TR("window_opacity"));
ImGui::SetNextItemWidth(ctrlW);
{
char winop_fmt[16];
snprintf(winop_fmt, sizeof(winop_fmt), "%.0f%%%%", sp_window_opacity * 100.0f);
if (ImGui::SliderFloat("##WindowOpacity", &sp_window_opacity, 0.3f, 1.0f, winop_fmt,
ImGuiSliderFlags_AlwaysClamp)) {
}
}
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity"));
ImGui::EndDisabled(); // low-spec
ImGui::PopFont();
} // sp_effects_expanded
}
// Bottom padding
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
// Draw glass panel behind content (auto-sized)
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// WALLET — card (privacy/daemon toggles + collapsible tools)
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
{
float cbSpacing = Layout::spacingMd();
float fh = ImGui::GetFrameHeight();
float inner = ImGui::GetStyle().ItemInnerSpacing.x;
auto cbW = [&](const char* label) { return fh + inner + ImGui::CalcTextSize(label).x; };
float totalW = cbW(TR("save_z_transactions")) + cbSpacing
+ cbW(TR("auto_shield")) + cbSpacing
+ cbW(TR("use_tor")) + cbSpacing
+ cbW(TR("keep_daemon")) + cbSpacing
+ cbW(TR("stop_external"));
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
float sp = cbSpacing * scale;
ImGui::Checkbox(TrId("save_z_transactions", "save_ztx").c_str(), &sp_save_ztxs);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_save_ztx"));
ImGui::SameLine(0, sp);
ImGui::Checkbox(TrId("auto_shield", "auto_shld").c_str(), &sp_auto_shield);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_auto_shield"));
ImGui::SameLine(0, sp);
ImGui::Checkbox(TrId("use_tor", "tor").c_str(), &sp_use_tor);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_tor"));
ImGui::SameLine(0, sp);
if (ImGui::Checkbox(TrId("keep_daemon", "keep_dmn").c_str(), &sp_keep_daemon_running)) {
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_keep_daemon"));
ImGui::SameLine(0, sp);
if (ImGui::Checkbox(TrId("stop_external", "stop_ext").c_str(), &sp_stop_external_daemon)) {
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_stop_external"));
ImGui::SameLine(0, sp);
if (ImGui::Checkbox(TrId("verbose_logging", "verbose").c_str(), &sp_verbose_logging)) {
dragonx::util::Logger::instance().setVerbose(sp_verbose_logging);
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", TR("tt_verbose"));
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// --- Collapsible: Tools & Actions... ---
{
const char* arrow = sp_tools_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
{
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
if (ImGui::Button("##ToolsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
sp_tools_expanded = !sp_tools_expanded;
}
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("tools_actions"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = body2;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
}
ImGui::PopStyleColor(3);
ImGui::PopFont();
}
if (sp_tools_expanded) {
float btnSpacing = Layout::spacingMd();
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
bw = std::max(minBtnW, bw);
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
AddressBookDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
ValidateAddressDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
RequestPaymentDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment"));
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining"));
ImGui::SameLine(0, btnSpacing);
if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge"));
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
sp_confirm_clear_ztx = true;
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx"));
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// BACKUP & DATA — card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
{
const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")};
const char* t1[] = {
TR("tt_import_key"),
TR("tt_export_key"),
TR("tt_export_all"),
TR("tt_backup"),
TR("tt_export_csv")
};
const char* wizLabel = TR("setup_wizard");
float sp = Layout::spacingSm();
ImFont* btnFont = S.resolveFont("button");
float btnPadX = btnPad * 2;
float naturalW = 0;
for (int i = 0; i < 5; i++)
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
float totalW = naturalW + wizW + sp * 6;
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
float scaledSp = sp * scale;
if (TactileButton(r1[0], ImVec2(0, 0), btnFont))
app->showImportKeyDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[0]);
ImGui::SameLine(0, scaledSp);
if (TactileButton(r1[1], ImVec2(0, 0), btnFont))
app->showExportKeyDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[1]);
ImGui::SameLine(0, scaledSp);
if (TactileButton(r1[2], ImVec2(0, 0), btnFont))
ExportAllKeysDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[2]);
ImGui::SameLine(0, scaledSp);
if (TactileButton(r1[3], ImVec2(0, 0), btnFont))
app->showBackupDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[3]);
ImGui::SameLine(0, scaledSp);
if (TactileButton(r1[4], ImVec2(0, 0), btnFont))
ExportTransactionsDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[4]);
// Right-align Setup Wizard
float curX = ImGui::GetCursorScreenPos().x;
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
if (scale < 1.0f) wizBtnW *= scale;
float rightEdge = cardMin.x + availWidth - pad;
float wizX = rightEdge - wizBtnW;
if (wizX > curX) {
ImGui::SameLine(0, 0);
ImGui::SetCursorScreenPos(ImVec2(wizX, ImGui::GetCursorScreenPos().y));
} else {
ImGui::SameLine(0, scaledSp);
}
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
app->restartWizard();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// NODE & SECURITY — card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node_security"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(130.0f);
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
auto rowBtnW = [&](std::initializer_list<const char*> labels) -> float {
float maxTextW = 0;
for (auto* l : labels) maxTextW = std::max(maxTextW, ImGui::CalcTextSize(l).x);
return std::max(minBtnW, maxTextW + btnPad * 2);
};
// --- NODE (left) + SECURITY (right) side by side ---
{
float colGap = Layout::spacingLg();
float leftColW = (contentW - colGap) * 0.6f;
float rightColW = (contentW - colGap) * 0.4f;
ImVec2 sectionOrigin = ImGui::GetCursorScreenPos();
float leftX = sectionOrigin.x;
float rightX = sectionOrigin.x + leftColW + colGap;
// ---- LEFT COLUMN: NODE ----
ImGui::SetCursorScreenPos(ImVec2(leftX, sectionOrigin.y));
ImGui::PushClipRect(ImVec2(leftX, sectionOrigin.y),
ImVec2(leftX + leftColW, sectionOrigin.y + 9999), true);
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
{
ImFont* body2Info = S.resolveFont("body2");
if (!body2Info) body2Info = Type().body2();
ImGui::PushFont(body2Info);
float rpcLblW = std::max(
S.drawElement("components.settings-page", "rpc-label-min-width").size,
std::min(leftColW * 0.35f, S.drawElement("components.settings-page", "rpc-label-width").size * hs));
std::string wallet_path = util::Platform::getDragonXDataDir() + "wallet.dat";
uint64_t wallet_size = util::Platform::getFileSize(wallet_path);
// Data Dir + Wallet Size — same line with "Label: value" format
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::AlignTextToFramePadding();
{
// Measure the wallet size string first
std::string size_str = (wallet_size > 0) ? util::Platform::formatFileSize(wallet_size) : TR("settings_not_found");
std::string dirPath = util::Platform::getDragonXDataDir();
// Calculate space taken by "Wallet Size: <size>" on the right
float walletSizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x + Layout::spacingXs();
float walletSizeValW = ImGui::CalcTextSize(size_str.c_str()).x;
float walletSizeTotalW = walletSizeLabelW + walletSizeValW + Layout::spacingLg();
// Available space for "Data Dir: <path>"
float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x + Layout::spacingXs();
float availForPath = leftColW - walletSizeTotalW - dataDirLabelW;
ImGui::TextUnformatted(TR("settings_data_dir"));
ImGui::SameLine(0, Layout::spacingXs());
ImVec2 pathSize = ImGui::CalcTextSize(dirPath.c_str());
ImVec4 linkCol = ImGui::ColorConvertU32ToFloat4(Primary());
ImVec4 linkHoverCol = linkCol;
linkHoverCol.w = std::min(1.0f, linkCol.w + 0.2f);
bool hovered = false;
if (pathSize.x > availForPath && availForPath > 0) {
float scale = availForPath / pathSize.x;
float condensedSize = body2Info->LegacySize * std::max(scale, 0.65f);
ImGui::SetWindowFontScale(condensedSize / body2Info->LegacySize);
ImGui::AlignTextToFramePadding();
ImGui::TextColored(linkCol, "%s", dirPath.c_str());
hovered = ImGui::IsItemHovered();
if (hovered) {
ImVec2 tMin = ImGui::GetItemRectMin();
ImVec2 tMax = ImGui::GetItemRectMax();
dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
}
ImGui::SetWindowFontScale(1.0f);
} else {
ImGui::TextColored(linkCol, "%s", dirPath.c_str());
hovered = ImGui::IsItemHovered();
if (hovered) {
ImVec2 tMin = ImGui::GetItemRectMin();
ImVec2 tMax = ImGui::GetItemRectMax();
dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
}
}
if (hovered) {
ImGui::SetTooltip("%s", TR("tt_open_dir"));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
if (ImGui::IsItemClicked())
util::Platform::openFolder(dirPath);
ImGui::SameLine(0, Layout::spacingLg());
ImGui::TextUnformatted(TR("settings_wallet_size_label"));
ImGui::SameLine(0, Layout::spacingXs());
if (wallet_size > 0) {
ImGui::TextUnformatted(size_str.c_str());
} else {
ImGui::TextDisabled("%s", TR("settings_not_found"));
}
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// RPC connection — two columns: (Host | Username) and (Port | Password)
float rpcHalfW = (leftColW - Layout::spacingMd()) * 0.5f;
float rpcHalfLblW = std::min(rpcLblW, rpcHalfW * 0.4f);
float rpcHalfInputW = std::max(40.0f, rpcHalfW - rpcHalfLblW);
float rpcRightColX = leftX + rpcHalfW + Layout::spacingMd();
// Row 1: RPC Host + Username
float row1Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(leftX, row1Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_host"));
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCHost", sp_rpc_host, sizeof(sp_rpc_host));
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_rpc_host"));
float afterRow1Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(rpcRightColX, row1Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_user"));
ImGui::SameLine(rpcRightColX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCUser", sp_rpc_user, sizeof(sp_rpc_user));
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_rpc_user"));
ImGui::SetCursorScreenPos(ImVec2(leftX, std::max(afterRow1Y, ImGui::GetCursorScreenPos().y)));
// Row 2: Port + Password
float row2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(leftX, row2Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_port"));
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCPort", sp_rpc_port, sizeof(sp_rpc_port));
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_rpc_port"));
float afterRow2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(rpcRightColX, row2Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_pass"));
ImGui::SameLine(rpcRightColX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCPassword", sp_rpc_password, sizeof(sp_rpc_password),
ImGuiInputTextFlags_Password);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_rpc_pass"));
ImGui::SetCursorScreenPos(ImVec2(leftX, std::max(afterRow2Y, ImGui::GetCursorScreenPos().y)));
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
TR("settings_auto_detected"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// Node maintenance buttons
{
ImFont* btnFont = S.resolveFont("button");
float nodeBtnW;
{
if (btnFont) ImGui::PushFont(btnFont);
nodeBtnW = rowBtnW({TR("test_connection"), TR("rescan")});
if (btnFont) ImGui::PopFont(/* btnFont */);
}
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::BeginDisabled(!app->isConnected());
if (TactileButton(TR("test_connection"), ImVec2(nodeBtnW, 0), btnFont)) {
if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
try {
rpc->call("getinfo");
return []() {
Notifications::instance().success("RPC connection OK");
};
} catch (const std::exception& e) {
std::string err = e.what();
return [err]() {
Notifications::instance().error("RPC error: " + err);
};
}
});
} else {
Notifications::instance().warning("Not connected to daemon");
}
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_test_conn"));
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TR("rescan"), ImVec2(nodeBtnW, 0), btnFont)) {
app->rescanBlockchain();
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan"));
ImGui::EndDisabled();
// Delete blockchain button (always available when using embedded daemon)
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y + Layout::spacingSm()));
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), btnFont)) {
sp_confirm_delete_blockchain = true;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain"));
ImGui::EndDisabled();
}
ImGui::PopFont();
}
float leftBottom = ImGui::GetCursorScreenPos().y;
ImGui::PopClipRect();
// ---- RIGHT COLUMN: SECURITY ----
ImGui::SetCursorScreenPos(ImVec2(rightX, sectionOrigin.y));
ImGui::PushClipRect(ImVec2(rightX, sectionOrigin.y),
ImVec2(rightX + rightColW, sectionOrigin.y + 9999), true);
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("security"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
{
// Encrypt / Change passphrase / Lock buttons
bool isEncrypted = app->state().isEncrypted();
bool isLocked = app->state().isLocked();
float secBtnW = std::min(rowBtnW({TR("settings_encrypt_wallet"), TR("settings_change_passphrase"), TR("settings_lock_now")}), (rightColW - Layout::spacingMd()) * 0.5f);
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
if (!isEncrypted) {
if (TactileButton(TR("settings_encrypt_wallet"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->showEncryptDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_encrypt"));
ImGui::SameLine(0, Layout::spacingMd());
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_not_encrypted"));
} else {
if (TactileButton(TR("settings_change_passphrase"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->showChangePassphraseDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_change_pass"));
ImGui::SameLine(0, Layout::spacingMd());
if (isLocked) {
ImGui::PushFont(Type().iconSmall());
ImGui::TextColored(ImVec4(1,0.7f,0.3f,1.0f), ICON_MD_LOCK);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
ImGui::TextColored(ImVec4(1,0.7f,0.3f,1.0f), "%s", TR("settings_locked"));
} else {
if (TactileButton(TR("settings_lock_now"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->lockWallet();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_lock"));
ImGui::SameLine(0, Layout::spacingSm());
ImGui::PushFont(Type().iconSmall());
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), ICON_MD_LOCK_OPEN);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_unlocked"));
}
// Remove Encryption button
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y + Layout::spacingXs()));
if (TactileButton(TR("settings_remove_encryption"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->showDecryptDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_remove_encrypt"));
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// Auto-Lock timeout
{
float comboW = S.drawElement("components.settings-page", "security-combo-width").sizeOr(120.0f);
int timeout = app->settings()->getAutoLockTimeout();
const char* timeoutLabels[] = { "Off", "1 min", "5 min", "15 min", "30 min", "1 hour" };
int timeoutValues[] = { 0, 60, 300, 900, 1800, 3600 };
int selTimeout = 0;
for (int i = 0; i < 6; i++) {
if (timeoutValues[i] == timeout) { selTimeout = i; break; }
}
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("settings_auto_lock"));
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::PushItemWidth(comboW);
if (ImGui::Combo("##autolock", &selTimeout, timeoutLabels, 6)) {
app->settings()->setAutoLockTimeout(timeoutValues[selTimeout]);
app->settings()->save();
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_auto_lock"));
ImGui::PopItemWidth();
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// PIN unlock section
bool isEncryptedPIN = app->state().isEncrypted();
if (isEncryptedPIN) {
bool hasPIN = app->hasPinVault();
float pinBtnW = std::min(rowBtnW({TR("settings_set_pin"), TR("settings_change_pin"), TR("settings_remove_pin")}), (rightColW - Layout::spacingSm()) * 0.5f);
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
if (!hasPIN) {
if (TactileButton(TR("settings_set_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
app->showPinSetupDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_set_pin"));
ImGui::SameLine(0, Layout::spacingMd());
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_quick_unlock_pin"));
} else {
if (TactileButton(TR("settings_change_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
app->showPinChangeDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_change_pin"));
ImGui::SameLine(0, Layout::spacingSm());
if (TactileButton(TR("settings_remove_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
app->showPinRemoveDialog();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_remove_pin"));
ImGui::SameLine(0, Layout::spacingMd());
ImGui::PushFont(Type().iconSmall());
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), ICON_MD_DIALPAD);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_pin_active"));
}
} else {
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::TextColored(ImVec4(1,1,1,0.3f), "%s", TR("settings_encrypt_first_pin"));
}
}
float rightBottom = ImGui::GetCursorScreenPos().y;
ImGui::PopClipRect();
// Advance cursor past both columns
float maxBottom = std::max(leftBottom, rightBottom);
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// EXPLORER & OPTIONS — full-width card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
float contentW = availWidth - pad * 2;
ImGui::PushFont(body2);
// Row 1: Transaction URL | Address URL (side-by-side)
float halfW = (contentW - Layout::spacingLg()) * 0.5f;
float lblTxW = ImGui::CalcTextSize("Transaction URL").x + Layout::spacingXs();
float lblAddrW = ImGui::CalcTextSize("Address URL").x + Layout::spacingXs();
float inputTxW = std::max(80.0f, halfW - lblTxW);
float inputAddrW = std::max(80.0f, halfW - lblAddrW);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("transaction_url"));
ImGui::SameLine(0, Layout::spacingXs());
ImGui::SetNextItemWidth(inputTxW);
ImGui::InputText("##TxExplorer", sp_tx_explorer, sizeof(sp_tx_explorer));
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_tx_url"));
ImGui::SameLine(pad + halfW + Layout::spacingLg());
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("address_url"));
ImGui::SameLine(0, Layout::spacingXs());
ImGui::SetNextItemWidth(inputAddrW);
ImGui::InputText("##AddrExplorer", sp_addr_explorer, sizeof(sp_addr_explorer));
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_addr_url"));
ImGui::PopFont();
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// Row 2: Checkboxes + Block Explorer button (on one line)
ImGui::Checkbox(TrId("custom_fees", "custom_fees").c_str(), &sp_allow_custom_fees);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_custom_fees"));
ImGui::SameLine(0, Layout::spacingLg());
ImGui::Checkbox(TrId("fetch_prices", "fetch_prices").c_str(), &sp_fetch_prices);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_fetch_prices"));
ImGui::SameLine(0, Layout::spacingLg());
if (TactileButton(TR("block_explorer"), ImVec2(0, 0), S.resolveFont("button"))) {
util::Platform::openUrl("https://explorer.dragonx.is");
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_block_explorer"));
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// ABOUT — card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("about"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
// Logo on the left side of the about card
ImTextureID logoTex = app->getLogoTexture();
float logoAreaW = 0;
if (logoTex != 0) {
float logoMaxH = schema::UI().drawElement("components.settings-page", "about-logo-size").sizeOr(64.0f);
float logoH = logoMaxH;
float aspect = (app->getLogoHeight() > 0) ? (float)app->getLogoWidth() / (float)app->getLogoHeight() : 1.0f;
float logoW = logoH * aspect;
ImVec2 logoPos = ImGui::GetCursorScreenPos();
dl->AddImage(logoTex,
ImVec2(logoPos.x, logoPos.y),
ImVec2(logoPos.x + logoW, logoPos.y + logoH));
logoAreaW = logoW + Layout::spacingLg();
ImGui::Indent(logoAreaW);
}
float contentW = availWidth - pad * 2 - logoAreaW;
// 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();
// Daemon version
{
const auto& st = app->state();
if (st.daemon_version > 0) {
int dmaj = st.daemon_version / 1000000;
int dmin = (st.daemon_version / 10000) % 100;
int dpat = (st.daemon_version / 100) % 100;
ImGui::PushFont(body2);
snprintf(buf, sizeof(buf), "%s: %d.%d.%d", TR("daemon_version"), dmaj, dmin, dpat);
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", buf);
ImGui::PopFont();
}
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::PushFont(body2);
ImGui::PushTextWrapPos(cardMin.x + availWidth - pad - logoAreaW);
ImGui::TextUnformatted(TR("settings_about_text"));
ImGui::PopTextWrapPos();
ImGui::PopFont();
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::PushFont(capFont);
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_copyright"));
ImGui::PopFont();
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
// Buttons — consistent equal-width row (full card width)
if (logoAreaW > 0) {
ImGui::Unindent(logoAreaW);
}
{
float fullContentW = availWidth - pad * 2;
float aboutBtnW = (fullContentW - Layout::spacingMd() * 3) / 4.0f;
if (TactileButton(TrId("website", "about_website").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
util::Platform::openUrl("https://dragonx.is");
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_website"));
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TrId("report_bug", "about_bug").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon/issues");
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_report_bug"));
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TrId("save_settings", "about_save").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
saveSettingsPageState(app->settings());
Notifications::instance().success("Settings saved");
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_save_settings"));
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TrId("reset_to_defaults", "about_reset").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
if (app->settings()) {
loadSettingsPageState(app->settings());
Notifications::instance().info("Settings reloaded from disk");
}
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_reset_settings"));
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
ImGui::Dummy(ImVec2(0, gap));
// ====================================================================
// DEBUG LOGGING — collapsible card with glass styling
// ====================================================================
{
// Clickable header row
ImVec2 headerPos = ImGui::GetCursorScreenPos();
const char* arrow = sp_debug_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
if (ImGui::Button("##DebugToggle", ImVec2(availWidth, ImGui::GetFrameHeight()))) {
sp_debug_expanded = !sp_debug_expanded;
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", sp_debug_expanded ? TR("tt_debug_collapse") : TR("tt_debug_expand"));
ImGui::PopStyleColor(3);
// Draw overline label + arrow on top of the invisible button
{
ImFont* ovFont = Type().overline();
float textY = headerPos.y + (ImGui::GetFrameHeight() - ovFont->LegacySize) * 0.5f;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(headerPos.x, textY), OnSurfaceMedium(), TR("debug_logging"));
// Use the icon font for the expand/collapse arrow
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = ovFont;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(headerPos.x + availWidth - arrowW - pad, textY), OnSurfaceMedium(), arrow);
}
if (sp_debug_expanded) {
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
dl->ChannelsSplit(2);
dl->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
ImGui::Indent(pad);
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_debug_select"));
ImGui::TextColored(ImVec4(1,1,1,0.35f), "%s", TR("settings_debug_restart_note"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// The 22 dragonxd debug categories
static const char* debugCats[] = {
"addrman", "alert", "bench", "coindb", "db", "estimatefee",
"http", "libevent", "lock", "mempool", "net",
"paymentdisclosure", "pow", "proxy", "prune", "rand",
"reindex", "rpc", "selectcoins", "tor", "zmq", "zrpc"
};
static const char* debugTips[] = {
"Peer address tracking and management",
"Alert system messages",
"Benchmark timings for operations",
"Coin database read/write operations",
"Berkeley DB operations",
"Fee estimation algorithm",
"HTTP RPC server activity",
"Libevent networking library",
"Lock contention debugging",
"Transaction memory pool activity",
"Network connections and messages",
"Payment disclosure protocol",
"Proof-of-work mining activity",
"SOCKS5 proxy connections",
"Block pruning operations",
"Random number generation",
"Blockchain reindexing progress",
"RPC command processing",
"Coin selection for transactions",
"Tor integration and circuit info",
"ZeroMQ notification system",
"Shielded (z-addr) RPC operations"
};
constexpr int numCats = sizeof(debugCats) / sizeof(debugCats[0]);
// Render as a 4-column grid of checkboxes
int columns = 4;
float dbgContentW = availWidth - pad * 2.0f;
float colW = dbgContentW / columns;
for (int i = 0; i < numCats; i++) {
if (i > 0 && (i % columns) != 0) {
ImGui::SameLine(pad + colW * (i % columns));
}
bool enabled = sp_debug_categories.count(debugCats[i]) > 0;
if (ImGui::Checkbox(debugCats[i], &enabled)) {
if (enabled) {
sp_debug_categories.insert(debugCats[i]);
} else {
sp_debug_categories.erase(debugCats[i]);
}
sp_debug_cats_dirty = true;
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", debugTips[i]);
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// "Restart daemon" button — only active when categories changed
if (sp_debug_cats_dirty) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 218, 0, 255));
ImFont* iconFont = Type().iconSmall();
if (iconFont) {
ImGui::PushFont(iconFont);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(ICON_MD_INFO);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
}
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("settings_debug_changed"));
ImGui::PopStyleColor();
ImGui::SameLine();
if (TactileButton(TR("settings_restart_daemon"), ImVec2(0, 0), S.resolveFont("button"))) {
sp_debug_cats_dirty = false;
app->restartDaemon();
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_restart_daemon"));
}
ImGui::Dummy(ImVec2(0, bottomPad));
ImGui::Unindent(pad);
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
dl->ChannelsSetCurrent(0);
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
dl->ChannelsMerge();
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
ImGui::Dummy(ImVec2(availWidth, 0));
}
}
// --- Shader-based scroll fade: unbind (restore ImGui's default shader) ---
if (fadeH > 0.0f && !sp_low_spec_mode && s_fadeShader.ready) {
effects::ScrollFadeShader::addUnbind(dl);
}
// --- Vertex-based alpha fade for ForegroundDrawList theme effects ---
// DrawGlassPanel draws rainbow borders, shimmer, specular glare, and
// edge traces on the ForegroundDrawList which bypasses the shader.
// Apply the same fade boundaries via vertex alpha manipulation.
if (fadeH > 0.0f) {
int fgVtxEnd = fgDL->VtxBuffer.Size;
float safeTop = settingsFadeTopY + settingsFadeZoneTop;
float safeBot = settingsFadeBottomY - settingsFadeZoneBot;
for (int vi = fgVtxStart; vi < fgVtxEnd; vi++) {
ImDrawVert& v = fgDL->VtxBuffer[vi];
if (v.pos.y >= safeTop && v.pos.y <= safeBot)
continue;
float alpha = 1.0f;
if (settingsFadeZoneTop > 0.0f) {
float dTop = v.pos.y - settingsFadeTopY;
if (dTop < settingsFadeZoneTop)
alpha = std::min(alpha, std::max(0.0f, dTop / settingsFadeZoneTop));
}
if (settingsFadeZoneBot > 0.0f) {
float dBot = settingsFadeBottomY - v.pos.y;
if (dBot < settingsFadeZoneBot)
alpha = std::min(alpha, std::max(0.0f, dBot / settingsFadeZoneBot));
}
if (alpha < 1.0f) {
int a = (v.col >> IM_COL32_A_SHIFT) & 0xFF;
a = static_cast<int>(a * alpha);
v.col = (v.col & ~IM_COL32_A_MASK) | (static_cast<ImU32>(a) << IM_COL32_A_SHIFT);
}
}
}
ImGui::EndChild(); // ##SettingsPageScroll
// Confirmation dialog for clearing z-tx history
if (sp_confirm_clear_ztx) {
if (BeginOverlayDialog(TR("confirm_clear_ztx_title"), &sp_confirm_clear_ztx, 480.0f, 0.94f)) {
ImGui::PushFont(Type().iconLarge());
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("warning"));
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();
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 40))) {
sp_confirm_clear_ztx = false;
}
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(TrId("clear_anyway", "clear_ztx_btn").c_str(), ImVec2(btnW, 40))) {
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");
}
sp_confirm_clear_ztx = false;
}
ImGui::PopStyleColor(2);
EndOverlayDialog();
}
}
// Confirmation dialog for deleting blockchain data
if (sp_confirm_delete_blockchain) {
if (BeginOverlayDialog(TR("confirm_delete_blockchain_title"), &sp_confirm_delete_blockchain, 500.0f, 0.94f)) {
ImGui::PushFont(Type().iconLarge());
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("warning"));
ImGui::Spacing();
ImGui::TextWrapped("%s", TR("confirm_delete_blockchain_msg"));
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_delete_blockchain_safe"));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 40))) {
sp_confirm_delete_blockchain = false;
}
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(TrId("delete_blockchain_confirm", "del_bc_btn").c_str(), ImVec2(btnW, 40))) {
app->deleteBlockchainData();
sp_confirm_delete_blockchain = false;
}
ImGui::PopStyleColor(2);
EndOverlayDialog();
}
}
}
} // namespace ui
} // namespace dragonx