From fffab228d1a77c02816b38b328635e23fb8e5887 Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 3 Oct 2026 19:52:09 -0500 Subject: [PATCH] fix(ui): apply the saved appearance settings at startup Low-spec mode, theme effects, blur, UI and noise opacity and the console scanline reached the live effects state only once the Settings page or the first-run wizard was opened. "Effects off" and low-spec therefore reverted at every launch, and the animated defaults kept the main loop busy (91% of a core on an idle Lite wallet whose effects were saved off). - App::applyVisualSettings(): one low-spec-aware path from the saved prefs to the effects state, used at startup (after the skin, since setActiveSkin reloads the effect config), by the low-spec hotkey, the wizard and the Settings page. - Low-spec stays a runtime-only override. The Settings page shows the overrides but persists the real prefs from its snapshot, and opening it under low-spec no longer turns theme effects and blur back on. Unticking a low-spec in the wizard that was loaded rather than ticked there now restores the saved prefs. - Theme effects on/off always pairs with reduced transparency (the rainbow, shimmer and glare layers), as at startup and in the hotkeys. - kAcrylicMaxBlur moves to imgui_acrylic.h, so startup clamps blur values saved in the old 0-4 range the way the slider does. Co-Authored-By: Claude Opus 5.5 --- src/app.cpp | 60 +++++++++++++++++++++------------ src/app.h | 8 +++++ src/app_wizard.cpp | 13 ++++---- src/ui/effects/imgui_acrylic.h | 5 +++ src/ui/pages/settings_page.cpp | 61 ++++++++++++++++++++++------------ 5 files changed, 97 insertions(+), 50 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 38246b3..9335608 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -491,6 +491,12 @@ bool App::init() skinMgr.setActiveSkin(skinId); } + // Apply the saved appearance prefs (low-spec, theme effects, blur, opacity, noise). Until now they + // only took effect once the Settings page or wizard was opened, so "effects off" / low-spec reverted + // at every launch and the animated defaults kept the main loop rendering continuously. After the + // skin, because setActiveSkin reloads the effect config (not the enabled flag). + applyVisualSettings(); + // Apply saved language std::string lang = settings_->getLanguage(); if (!lang.empty()) { @@ -583,6 +589,31 @@ bool App::init() // ============================================================================ // Pre-frame: font atlas rebuilds (must run BEFORE ImGui::NewFrame) // ============================================================================ +void App::applyVisualSettings() +{ + if (!settings_) return; + namespace fx = ui::effects; + const bool lowSpec = settings_->getLowSpecMode(); + fx::setLowSpecMode(lowSpec); + if (lowSpec) { + // Low-spec overrides the heavy effects at runtime only; the saved prefs stay as they are so + // turning low-spec off restores them. + fx::ImGuiAcrylic::ApplyBlurAmount(0.0f); + fx::ImGuiAcrylic::SetUIOpacity(1.0f); + fx::ThemeEffects::instance().setEnabled(false); + fx::ThemeEffects::instance().setReducedTransparency(true); + ui::ConsoleTab::s_scanline_enabled = false; + } else { + const bool effects = settings_->getThemeEffectsEnabled(); + fx::ImGuiAcrylic::ApplyBlurAmount(std::min(settings_->getBlurMultiplier(), fx::kAcrylicMaxBlur)); + fx::ImGuiAcrylic::SetUIOpacity(settings_->getUIOpacity()); + fx::ThemeEffects::instance().setEnabled(effects); + fx::ThemeEffects::instance().setReducedTransparency(!effects); + ui::ConsoleTab::s_scanline_enabled = settings_->getScanlineEnabled(); + } + fx::ImGuiAcrylic::SetNoiseOpacity(settings_->getNoiseOpacity()); +} + void App::preFrame() { ImGuiIO& io = ImGui::GetIO(); @@ -1465,28 +1496,15 @@ void App::handleGlobalShortcuts() // (checked BEFORE Ctrl+Down so it doesn't also trigger theme effects) if (io.KeyCtrl && io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { bool newLow = !ui::effects::isLowSpecMode(); - ui::effects::setLowSpecMode(newLow); - if (newLow) { - // Disable all heavy effects at runtime (don't overwrite saved prefs) - ui::effects::ImGuiAcrylic::ApplyBlurAmount(0.0f); - ui::effects::ImGuiAcrylic::SetUIOpacity(1.0f); - ui::effects::ThemeEffects::instance().setEnabled(false); - ui::effects::ThemeEffects::instance().setReducedTransparency(true); - if (settings_) { - settings_->setLowSpecMode(true); - settings_->setWindowOpacity(1.0f); - settings_->save(); - } + if (settings_) { + // Persist the mode (and the opaque window low-spec implies) but not the effect prefs: + // applyVisualSettings overrides them at runtime, so turning low-spec off restores them. + settings_->setLowSpecMode(newLow); + if (newLow) settings_->setWindowOpacity(1.0f); + settings_->save(); + applyVisualSettings(); } else { - // Restore effect settings from saved preferences - if (settings_) { - settings_->setLowSpecMode(false); - settings_->save(); - ui::effects::ImGuiAcrylic::ApplyBlurAmount(settings_->getBlurMultiplier()); - ui::effects::ImGuiAcrylic::SetUIOpacity(settings_->getUIOpacity()); - ui::effects::ThemeEffects::instance().setEnabled(settings_->getThemeEffectsEnabled()); - ui::effects::ThemeEffects::instance().setReducedTransparency(!settings_->getThemeEffectsEnabled()); - } + ui::effects::setLowSpecMode(newLow); } ui::Notifications::instance().info(newLow ? TR("appx_low_spec_mode_enabled") : TR("appx_low_spec_mode_disabled")); } diff --git a/src/app.h b/src/app.h index f56ace8..a211ce1 100644 --- a/src/app.h +++ b/src/app.h @@ -125,6 +125,14 @@ public: float logicDeltaTime() const { return logic_dt_; } static constexpr float kMaxLogicDeltaSeconds = 6.0f; + /** + * @brief Push the saved appearance prefs (low-spec, theme effects, blur, UI and noise opacity, + * console scanline) to the live effects state. Low-spec overrides the rest without touching the + * saved values. Called at startup, by the low-spec hotkey, the first-run wizard and the Settings + * page, so every path applies the same thing. + */ + void applyVisualSettings(); + /** * @brief Pre-frame tasks that must run BEFORE ImGui::NewFrame(). * diff --git a/src/app_wizard.cpp b/src/app_wizard.cpp index e10f04c..1a41b73 100644 --- a/src/app_wizard.cpp +++ b/src/app_wizard.cpp @@ -357,13 +357,9 @@ void App::renderFirstRunWizard() { if (lang.first == wiz_cur_lang) { wiz_language_index = idx; break; } idx++; } - // Apply loaded settings to runtime so visuals match slider values - ui::effects::setLowSpecMode(wiz_low_spec); - ui::effects::ImGuiAcrylic::ApplyBlurAmount(wiz_blur_amount); - ui::effects::ImGuiAcrylic::SetUIOpacity(wiz_ui_opacity); - ui::effects::ThemeEffects::instance().setEnabled(wiz_theme_effects); - ui::effects::ThemeEffects::instance().setReducedTransparency(!wiz_theme_effects); - ui::ConsoleTab::s_scanline_enabled = wiz_scanline; + // Apply the loaded settings to the runtime (same low-spec-aware path as startup) so the + // visuals match the slider values + applyVisualSettings(); wiz_appearance_init = true; } @@ -538,6 +534,9 @@ void App::renderFirstRunWizard() { settings_->setScanlineEnabled(wiz_scanline); settings_->setLowSpecMode(wiz_low_spec); settings_->save(); + // Make the runtime match what was just saved, both ways: unticking a low-spec that was + // loaded rather than ticked here has no snapshot, so the branch above pushes nothing. + applyVisualSettings(); } ImGui::PopStyleVar(); ImGui::SameLine(); diff --git a/src/ui/effects/imgui_acrylic.h b/src/ui/effects/imgui_acrylic.h index 936badb..d56a817 100644 --- a/src/ui/effects/imgui_acrylic.h +++ b/src/ui/effects/imgui_acrylic.h @@ -11,6 +11,11 @@ namespace dragonx { namespace ui { namespace effects { +// Top of the Acrylic slider's range (the blur-radius multiplier at 100%). The panels frost at full +// alpha now, so this only controls how BLURRY the max is — kept gentle (the old max of 4.0 was way +// too strong). Saved values from the old 0–4 range are clamped to it wherever they're applied. +inline constexpr float kAcrylicMaxBlur = 1.25f; + /** * @brief ImGui integration for acrylic effects * diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 8d0074e..96e7a13 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -60,10 +60,8 @@ namespace ui { using namespace material; -// Top of the Acrylic slider's range (the blur-radius multiplier at 100%). The panels frost at full -// alpha now, so this only controls how BLURRY the max is — kept gentle (the old max of 4.0 was way -// too strong). The slider still reads 0–100%; this is just what 100% maps to. -static constexpr float kAcrylicMaxBlur = 1.25f; +// Top of the Acrylic slider's range; the slider reads 0–100% of it (shared with App::applyVisualSettings). +static constexpr float kAcrylicMaxBlur = effects::kAcrylicMaxBlur; // Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID static std::string TrId(const char* tr_key, const char* id) { @@ -212,6 +210,7 @@ static void enterLowSpec(bool applyEffects) { effects::ImGuiAcrylic::ApplyBlurAmount(0.0f); effects::ImGuiAcrylic::SetUIOpacity(1.0f); effects::ThemeEffects::instance().setEnabled(false); + effects::ThemeEffects::instance().setReducedTransparency(true); ConsoleTab::s_scanline_enabled = false; } } @@ -230,6 +229,7 @@ static void exitLowSpec(bool applyEffects) { effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount); effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity); effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled); + effects::ThemeEffects::instance().setReducedTransparency(!s_settingsState.theme_effects_enabled); ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled; } s_settingsState.low_spec_snapshot.valid = false; @@ -374,7 +374,8 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) { // (APPEARANCE card now uses ChannelsSplit like all other cards) -static void loadSettingsPageState(config::Settings* settings) { +static void loadSettingsPageState(App* app) { + config::Settings* settings = app ? app->settings() : nullptr; if (!settings) return; s_settingsState.save_ztxs = settings->getSaveZtxs(); @@ -413,10 +414,8 @@ static void loadSettingsPageState(config::Settings* settings) { s_settingsState.balance_layout = settings->getBalanceLayout(); s_settingsState.scanline_enabled = settings->getScanlineEnabled(); - ConsoleTab::s_scanline_enabled = s_settingsState.scanline_enabled; s_settingsState.theme_effects_enabled = settings->getThemeEffectsEnabled(); s_settingsState.low_spec_mode = settings->getLowSpecMode(); - effects::setLowSpecMode(s_settingsState.low_spec_mode); s_settingsState.reduce_motion = settings->getReduceMotion(); s_settingsState.font_scale = settings->getFontScale(); Layout::setUserFontScale(s_settingsState.font_scale); // sync with Layout on load @@ -444,11 +443,20 @@ static void loadSettingsPageState(config::Settings* settings) { snprintf(s_settingsState.rpc_password, sizeof(s_settingsState.rpc_password), "%s", rpcCfg.rpcpassword.c_str()); s_settingsState.rpc_plaintext_remote = rpc::Connection::usesPlaintextRemote(rpcCfg); - // Apply loaded visual effects settings - effects::ImGuiAcrylic::ApplyBlurAmount(s_settingsState.blur_amount); - effects::ImGuiAcrylic::SetUIOpacity(s_settingsState.ui_opacity); - effects::ImGuiAcrylic::SetNoiseOpacity(s_settingsState.noise_opacity); - effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled); + // Apply the loaded appearance prefs through the same low-spec-aware path as startup (it used to push + // the raw prefs, so opening Settings turned theme effects and blur back on under low-spec). Under + // low-spec the controls show the overrides and the snapshot keeps the saved prefs, so unticking + // low-spec restores them; enterLowSpec(false) only mirrors what applyVisualSettings applied. + app->applyVisualSettings(); + if (!s_settingsState.low_spec_mode) { + s_settingsState.low_spec_snapshot.valid = false; + } else { + // Keep a snapshot already taken this session (a Reload after ticking low-spec here): it still + // holds the prefs from before low-spec, e.g. the window opacity low-spec saved as 1.0. + const LowSpecSnapshot kept = s_settingsState.low_spec_snapshot; + enterLowSpec(false); + if (kept.valid) s_settingsState.low_spec_snapshot = kept; + } s_settingsState.initialized = true; } @@ -473,16 +481,22 @@ static void saveSettingsPageState(config::Settings* settings) { settings->setLanguage(it->first); } - // Visual effects settings - settings->setAcrylicEnabled(s_settingsState.acrylic_enabled); - settings->setAcrylicQuality(s_settingsState.blur_amount > 0.001f ? static_cast(effects::AcrylicQuality::Low) : static_cast(effects::AcrylicQuality::Off)); - settings->setBlurMultiplier(s_settingsState.blur_amount); - settings->setUIOpacity(s_settingsState.ui_opacity); - settings->setWindowOpacity(s_settingsState.window_opacity); + // Visual effects settings. Under low-spec the controls show the low-spec overrides; persist the + // user's real prefs (held in the snapshot) instead, so low-spec stays a runtime-only override and + // turning it off restores them, the same as the hotkey. + const bool lowSpecOverrides = s_settingsState.low_spec_mode && s_settingsState.low_spec_snapshot.valid; + const LowSpecSnapshot& realPrefs = s_settingsState.low_spec_snapshot; + const float blur = lowSpecOverrides ? realPrefs.blur_amount : s_settingsState.blur_amount; + settings->setAcrylicEnabled(lowSpecOverrides ? realPrefs.acrylic_enabled : s_settingsState.acrylic_enabled); + settings->setAcrylicQuality(blur > 0.001f ? static_cast(effects::AcrylicQuality::Low) : static_cast(effects::AcrylicQuality::Off)); + settings->setBlurMultiplier(blur); + settings->setUIOpacity(lowSpecOverrides ? realPrefs.ui_opacity : s_settingsState.ui_opacity); + settings->setWindowOpacity(s_settingsState.window_opacity); // 1.0 under low-spec, as the hotkey saves it settings->setNoiseOpacity(s_settingsState.noise_opacity); settings->setGradientBackground(s_settingsState.gradient_background); - settings->setScanlineEnabled(s_settingsState.scanline_enabled); - settings->setThemeEffectsEnabled(s_settingsState.theme_effects_enabled); + settings->setScanlineEnabled(lowSpecOverrides ? realPrefs.scanline_enabled : s_settingsState.scanline_enabled); + settings->setThemeEffectsEnabled(lowSpecOverrides ? realPrefs.theme_effects_enabled + : s_settingsState.theme_effects_enabled); settings->setLowSpecMode(s_settingsState.low_spec_mode); settings->setReduceMotion(s_settingsState.reduce_motion); settings->setFontScale(s_settingsState.font_scale); @@ -616,7 +630,7 @@ static void renderSettingsTabBar(float availWidth) { void RenderSettingsPage(App* app) { // Load settings state on first render if (!s_settingsState.initialized && app->settings()) { - loadSettingsPageState(app->settings()); + loadSettingsPageState(app); } // Sync low-spec / theme-effects state from runtime each frame @@ -1022,6 +1036,9 @@ void RenderSettingsPage(App* app) { efFlow(TR("theme_effects")); if (ImGui::Checkbox(TrId("theme_effects", "effects").c_str(), &s_settingsState.theme_effects_enabled)) { effects::ThemeEffects::instance().setEnabled(s_settingsState.theme_effects_enabled); + // Paired with the enable flag everywhere (startup, hotkeys): off also drops the + // translucent rainbow/shimmer/glare layers, so turning effects on has to restore them. + effects::ThemeEffects::instance().setReducedTransparency(!s_settingsState.theme_effects_enabled); saveSettingsPageState(app->settings()); } if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_theme_effects")); @@ -2664,7 +2681,7 @@ void RenderSettingsPage(App* app) { if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_settings")); af.next(material::ActionButtonWidth(TR("reset_to_defaults"), ICON_MD_RESTORE)); if (material::ActionButton("##aboutreset", TR("reset_to_defaults"), ICON_MD_RESTORE, AT::Tertiary)) { - if (app->settings()) { loadSettingsPageState(app->settings()); Notifications::instance().info(TR("settings_reloaded")); } + if (app->settings()) { loadSettingsPageState(app); Notifications::instance().info(TR("settings_reloaded")); } } if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reset_settings")); }