From 95605dd7e9f11140e4f6947ddf4b587d0a32cd8a Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 17:42:29 -0500 Subject: [PATCH] fix(ui): stop global shortcuts auto-repeating; skip invalid skins The global keyboard shortcuts tested keys without disabling key-repeat, so holding one down fired it every frame: the theme cycle, balance and market layout cycles, the effects and background toggles, the settings and sidebar-collapse keys all re-triggered on a held key. Cycling themes also stepped onto skins that had failed to load and wrote the broken pick into settings. - Every one-shot shortcut now checks for a fresh key press only, so a held key fires once. - Theme cycling skips over invalid skins and only applies, persists, and shows the toast when a skin actually loads; if none can load, settings are left untouched and no toast appears. Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 39 ++++++++++++++++++++-------------- src/ui/sidebar.h | 2 +- src/ui/windows/balance_tab.cpp | 4 ++-- src/ui/windows/market_tab.cpp | 4 ++-- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 190ef42..b9478b8 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1705,14 +1705,14 @@ void App::handleGlobalShortcuts() ImGuiIO& io = ImGui::GetIO(); // Keyboard shortcut: Ctrl+, to open Settings page - if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma)) { + if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma, false)) { setCurrentPage(ui::NavPage::Settings); } // Keyboard shortcut: Ctrl+Left/Right to cycle themes if (io.KeyCtrl && !io.WantTextInput) { - bool prevTheme = ImGui::IsKeyPressed(ImGuiKey_LeftArrow); - bool nextTheme = ImGui::IsKeyPressed(ImGuiKey_RightArrow); + bool prevTheme = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false); + bool nextTheme = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false); if (prevTheme || nextTheme) { auto& skinMgr = ui::schema::SkinManager::instance(); const auto& skins = skinMgr.available(); @@ -1721,16 +1721,23 @@ void App::handleGlobalShortcuts() for (int i = 0; i < (int)skins.size(); i++) { if (skins[i].id == skinMgr.activeSkinId()) { cur = i; break; } } - if (prevTheme) - cur = (cur - 1 + (int)skins.size()) % (int)skins.size(); - else - cur = (cur + 1) % (int)skins.size(); - skinMgr.setActiveSkin(skins[cur].id); - if (settings_) { - settings_->setSkinId(skins[cur].id); - settings_->save(); + const int n = (int)skins.size(); + const int step = prevTheme ? n - 1 : 1; // -1 (mod n) when cycling back + int pick = cur; + bool applied = false; + for (int k = 0; k < n && !applied; k++) { + pick = (pick + step) % n; + if (!skins[pick].valid) continue; + applied = skinMgr.setActiveSkin(skins[pick].id); } - ui::Notifications::instance().info(std::string(TR("appx_theme_prefix")) + skins[cur].name); + if (applied) { + if (settings_) { + settings_->setSkinId(skins[pick].id); + settings_->save(); + } + ui::Notifications::instance().info(std::string(TR("appx_theme_prefix")) + skins[pick].name); + } + // else: every skin invalid / failed to load; leave settings untouched, no toast } } } @@ -1742,7 +1749,7 @@ void App::handleGlobalShortcuts() // Keyboard shortcut: Ctrl+Shift+Down to toggle low-spec mode // (checked BEFORE Ctrl+Down so it doesn't also trigger theme effects) - if (io.KeyCtrl && io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { + if (io.KeyCtrl && io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_DownArrow, false)) { bool newLow = !ui::effects::isLowSpecMode(); if (settings_) { // Persist the mode (and the opaque window low-spec implies) but not the effect prefs: @@ -1758,7 +1765,7 @@ void App::handleGlobalShortcuts() } // Keyboard shortcut: Ctrl+Down to toggle theme effects (Shift excluded) - else if (io.KeyCtrl && !io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { + else if (io.KeyCtrl && !io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_DownArrow, false)) { bool newState = !ui::effects::ThemeEffects::instance().isEnabled(); ui::effects::ThemeEffects::instance().setEnabled(newState); ui::effects::ThemeEffects::instance().setReducedTransparency(!newState); @@ -1770,7 +1777,7 @@ void App::handleGlobalShortcuts() } // Keyboard shortcut: Ctrl+Up to toggle simple gradient background - if (io.KeyCtrl && !io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) { + if (io.KeyCtrl && !io.KeyShift && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_UpArrow, false)) { bool newGrad = !settings_->getGradientBackground(); settings_->setGradientBackground(newGrad); ui::schema::SkinManager::instance().setGradientMode(newGrad); @@ -1781,7 +1788,7 @@ void App::handleGlobalShortcuts() // Debug: Ctrl+Shift+W to re-show first-run wizard (set to false to disable) constexpr bool ENABLE_WIZARD_HOTKEY = false; if constexpr (ENABLE_WIZARD_HOTKEY) { - if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_W)) { + if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_W, false)) { wizard_phase_ = WizardPhase::Appearance; DEBUG_LOGF("[Debug] Wizard re-opened via Ctrl+Shift+W\n"); } diff --git a/src/ui/sidebar.h b/src/ui/sidebar.h index 758c6fc..924f825 100644 --- a/src/ui/sidebar.h +++ b/src/ui/sidebar.h @@ -774,7 +774,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei do { idx++; } while (idx < (int)NavPage::Count_ && !IsNavPageVisible(static_cast(idx))); if (idx < (int)NavPage::Count_) nav = true; } - if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) + if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket, false)) collapsed = !collapsed; if (nav) { current = static_cast(idx); changed = true; } } diff --git a/src/ui/windows/balance_tab.cpp b/src/ui/windows/balance_tab.cpp index 0014cca..86bd58b 100644 --- a/src/ui/windows/balance_tab.cpp +++ b/src/ui/windows/balance_tab.cpp @@ -172,8 +172,8 @@ void RenderBalanceTab(App* app) // Left/Right arrows: cycle through enabled balance layouts // (skip when Ctrl is held — Ctrl+Arrow cycles themes instead) if (app->settings() && !ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) { - bool cycleUp = ImGui::IsKeyPressed(ImGuiKey_LeftArrow); - bool cycleDown = ImGui::IsKeyPressed(ImGuiKey_RightArrow); + bool cycleUp = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false); + bool cycleDown = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false); if (cycleUp || cycleDown) { const auto& layouts = GetBalanceLayouts(); // Build list of enabled layout IDs diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 31f9087..4683567 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -2257,8 +2257,8 @@ void RenderMarketTab(App* app) // portfolio editor / market-settings modal is open. if (app->settings() && !s_pfEdit.open && !ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) { - bool prev = ImGui::IsKeyPressed(ImGuiKey_LeftArrow); - bool next = ImGui::IsKeyPressed(ImGuiKey_RightArrow); + bool prev = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false); + bool next = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false); if (prev || next) { int st = app->settings()->getPortfolioStyle(); st = next ? (st + 1) % 3 : (st + 2) % 3;