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 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:52:09 -05:00
co-authored by Claude Opus 5.5
parent 402a3053dc
commit fffab228d1
5 changed files with 97 additions and 50 deletions
+39 -22
View File
@@ -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<int>(effects::AcrylicQuality::Low) : static_cast<int>(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<int>(effects::AcrylicQuality::Low) : static_cast<int>(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"));
}