From 7c4fb8b08404d4153211d5d38bb08dbc4c3ad8bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 16 Apr 2026 13:15:22 -0600 Subject: [PATCH 1/3] Add basic modifier key tracking to UIScreen --- Common/Input/InputState.h | 6 ++++++ Common/UI/UIScreen.cpp | 32 ++++++++++++++++++++++++++++++++ Common/UI/UIScreen.h | 17 +++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h index bdbec037d7..c48737cb84 100644 --- a/Common/Input/InputState.h +++ b/Common/Input/InputState.h @@ -171,7 +171,13 @@ enum class KeyInputFlags { HAS_WHEEL_DELTA = 1 << 2, IS_REPEAT = 1 << 3, CHAR = 1 << 4, // Unicode character input. Cannot detect keyups of these so KeyInputFlags::DOWN and KeyInputFlags::UP are zero when this is set. + + // NOTE: These are only available in sync input. + MOD_CTRL = 1 << 5, + MOD_SHIFT = 1 << 6, + MOD_ALT = 1 << 7, }; + ENUM_CLASS_BITOPS(KeyInputFlags); struct KeyInput { diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index 8566822af6..f223c02185 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -130,9 +130,41 @@ bool UIScreen::UnsyncKey(const KeyInput &key) { } } + // Track modifier keys. + if (key.flags & KeyInputFlags::DOWN) { + switch (key.keyCode) { + case NKCODE_CTRL_LEFT: modifiersPressed_ |= Modifier::LCTRL; break; + case NKCODE_CTRL_RIGHT: modifiersPressed_ |= Modifier::RCTRL; break; + case NKCODE_SHIFT_LEFT: modifiersPressed_ |= Modifier::LSHIFT; break; + case NKCODE_SHIFT_RIGHT: modifiersPressed_ |= Modifier::RSHIFT; break; + case NKCODE_ALT_LEFT: modifiersPressed_ |= Modifier::LALT; break; + case NKCODE_ALT_RIGHT: modifiersPressed_ |= Modifier::RALT; break; + } + } + if (key.flags & KeyInputFlags::UP) { + switch (key.keyCode) { + case NKCODE_CTRL_LEFT: modifiersPressed_ &= ~Modifier::LCTRL; break; + case NKCODE_CTRL_RIGHT: modifiersPressed_ &= ~Modifier::RCTRL; break; + case NKCODE_SHIFT_LEFT: modifiersPressed_ &= ~Modifier::LSHIFT; break; + case NKCODE_SHIFT_RIGHT: modifiersPressed_ &= ~Modifier::RSHIFT; break; + case NKCODE_ALT_LEFT: modifiersPressed_ &= ~Modifier::LALT; break; + case NKCODE_ALT_RIGHT: modifiersPressed_ &= ~Modifier::RALT; break; + } + } + QueuedEvent ev{}; ev.type = QueuedEventType::KEY; ev.key = key; + + if (modifiersPressed_ & (Modifier::LCTRL | Modifier::RCTRL)) { + ev.key.flags |= KeyInputFlags::MOD_CTRL; + } + if (modifiersPressed_ & (Modifier::LSHIFT | Modifier::RSHIFT)) { + ev.key.flags |= KeyInputFlags::MOD_SHIFT; + } + if (modifiersPressed_ & (Modifier::LALT | Modifier::RALT)) { + ev.key.flags |= KeyInputFlags::MOD_ALT; + } std::lock_guard guard(eventQueueLock_); eventQueue_.push_back(ev); return retval; diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index e319b097d2..8732752067 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -32,6 +32,17 @@ struct QueuedEvent { }; }; +enum class Modifier { + NONE = 0, + LCTRL = 1, + RCTRL = 2, + LSHIFT = 4, + RSHIFT = 8, + LALT = 16, + RALT = 32, +}; +ENUM_CLASS_BITOPS(Modifier); + class UIScreen : public Screen { public: UIScreen(); @@ -61,6 +72,10 @@ public: virtual UI::Margins RootMargins() const { return UI::Margins(0); } + virtual void focusChanged(ScreenFocusChange focusChange) { + modifiersPressed_ = Modifier::NONE; + } + protected: virtual void CreateViews() = 0; @@ -90,6 +105,8 @@ protected: private: std::mutex eventQueueLock_; std::deque eventQueue_; + + Modifier modifiersPressed_{}; }; class UIDialogScreen : public UIScreen { From 9f1e0eadd003c49b8dc569169a5f1f0042ddc710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 16 Apr 2026 13:27:37 -0600 Subject: [PATCH 2/3] Support Ctrl+Tab / Ctrl+Shift+Tab to switch tabs in the UI --- Common/Input/InputState.h | 4 ++++ Common/UI/TabHolder.cpp | 18 ++++++++++++++++++ Common/UI/TabHolder.h | 1 + 3 files changed, 23 insertions(+) diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h index c48737cb84..bf7da2dd37 100644 --- a/Common/Input/InputState.h +++ b/Common/Input/InputState.h @@ -165,6 +165,10 @@ struct TouchInput { double timestamp; }; +#undef MOD_CTRL +#undef MOD_ALT +#undef MOD_SHIFT + enum class KeyInputFlags { DOWN = 1 << 0, UP = 1 << 1, diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index 2a29c11267..6d94bb4004 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -333,6 +333,24 @@ bool ChoiceStrip::Key(const KeyInput &input) { } return true; } + + // Support Ctrl+Tab / Ctrl+Shift+Tab as well, as these are common shortcuts for tab switching even outside of browsers. + if (input.keyCode == NKCODE_TAB && (input.flags & KeyInputFlags::MOD_CTRL)) { + if (input.flags & KeyInputFlags::MOD_SHIFT) { + if (selected_ > 0) { + SetSelection(selected_ - 1, true); + } else if (!choices_.empty()) { + SetSelection(choices_.size() - 1, true); + } + } else { + if (selected_ < (int)choices_.size() - 1) { + SetSelection(selected_ + 1, true); + } else { + SetSelection(0, true); + } + } + return true; + } } return ViewGroup::Key(input); } diff --git a/Common/UI/TabHolder.h b/Common/UI/TabHolder.h index 2ce46a5b20..096efbb061 100644 --- a/Common/UI/TabHolder.h +++ b/Common/UI/TabHolder.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "Common/UI/View.h" #include "Common/UI/ViewGroup.h" From c2655eaf8268a70b732744aea58dd53a7107f0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 16 Apr 2026 13:57:27 -0600 Subject: [PATCH 3/3] Don't save the current tab between sessions. --- Core/Config.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 14be10ce8e..fe143fa119 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -317,9 +317,10 @@ static const ConfigSetting generalSettings[] = { ConfigSetting("UITint", SETTING(g_Config, fUITint), 0.0, CfgFlag::DEFAULT), ConfigSetting("UISaturation", SETTING(g_Config, fUISaturation), &DefaultUISaturation, CfgFlag::DEFAULT), - // Current tabs - ConfigSetting("SettingsCurrentTab", SETTING(g_Config, iSettingsCurrentTab), 0, CfgFlag::DEFAULT), - ConfigSetting("DeveloperSettingsCurrentTab", SETTING(g_Config, iDeveloperSettingsCurrentTab), 0, CfgFlag::DEFAULT), + // Current settings tabs + // Decided for now that these should not be saved, so commented out from here. Still saved within the session, of course. + // ConfigSetting("SettingsCurrentTab", SETTING(g_Config, iSettingsCurrentTab), 0, CfgFlag::DEFAULT), + // ConfigSetting("DeveloperSettingsCurrentTab", SETTING(g_Config, iDeveloperSettingsCurrentTab), 0, CfgFlag::DEFAULT), #if defined(USING_WIN_UI) ConfigSetting("TopMost", SETTING(g_Config, bTopMost), false, CfgFlag::DEFAULT),