From a6265c30898120018cd98383d54be217d0397ab5 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:06:22 +0100 Subject: [PATCH] add keyboard navigation support. --- sphaira/include/app.hpp | 1 + sphaira/include/ui/types.hpp | 75 ++++++++++++++++++++++++++++++++++++ sphaira/source/app.cpp | 70 +++++++++++++++++++++++++++++++-- 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/sphaira/include/app.hpp b/sphaira/include/app.hpp index dc39b6f..fc40375 100644 --- a/sphaira/include/app.hpp +++ b/sphaira/include/app.hpp @@ -267,6 +267,7 @@ public: PadState m_pad{}; TouchInfo m_touch_info{}; Controller m_controller{}; + KeyboardState m_keyboard{}; std::vector m_theme_meta_entries; Vec2 m_scale{1, 1}; diff --git a/sphaira/include/ui/types.hpp b/sphaira/include/ui/types.hpp index 77105d6..b849a49 100644 --- a/sphaira/include/ui/types.hpp +++ b/sphaira/include/ui/types.hpp @@ -366,6 +366,81 @@ struct Action final { std::string m_hint{}; }; +struct GenericHidState { + GenericHidState() { + Reset(); + } + + void Reset() { + buttons_cur = 0; + buttons_old = 0; + } + + u64 GetButtons() const { + return buttons_cur; + } + + u64 GetButtonsDown() const { + return buttons_cur & ~buttons_old; + } + + u64 GetButtonsUp() const { + return ~buttons_cur & buttons_old; + } + + virtual void Update() = 0; + +protected: + u64 buttons_cur; + u64 buttons_old; +}; + +struct KeyboardState final : GenericHidState { + struct MapEntry { + HidKeyboardKey key; + u64 button; + }; + using Map = std::span; + + void Init(Map map) { + m_map = map; + Reset(); + } + + void Update() override { + buttons_old = buttons_cur; + buttons_cur = 0; + + if (!hidGetKeyboardStates(&m_state, 1)) { + return; + } + + const auto ctrl = m_state.modifiers & HidKeyboardModifier_Control; + const auto shift = m_state.modifiers & HidKeyboardModifier_Shift; + + for (const auto& map : m_map) { + if (hidKeyboardStateGetKey(&m_state, map.key)) { + if (shift && map.button == static_cast(Button::L)) { + buttons_cur |= static_cast(Button::L2); + } else if (shift && map.button == static_cast(Button::R)) { + buttons_cur |= static_cast(Button::R2); + } else if (ctrl && map.button == static_cast(Button::L)) { + buttons_cur |= static_cast(Button::L3); + } else if (ctrl && map.button == static_cast(Button::R)) { + buttons_cur |= static_cast(Button::R3); + } else { + buttons_cur |= map.button; + } + + } + } + } + +private: + Map m_map{}; + HidKeyboardState m_state{}; +}; + struct Controller { u64 m_kdown{}; u64 m_kheld{}; diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index 397534c..146811e 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -74,6 +74,37 @@ struct NszOption { const char* name; }; +constexpr KeyboardState::MapEntry KEYBOARD_BUTTON_MAP[] = { + {HidKeyboardKey_UpArrow, static_cast(Button::DPAD_UP)}, + {HidKeyboardKey_DownArrow, static_cast(Button::DPAD_DOWN)}, + {HidKeyboardKey_LeftArrow, static_cast(Button::DPAD_LEFT)}, + {HidKeyboardKey_RightArrow, static_cast(Button::DPAD_RIGHT)}, + + {HidKeyboardKey_W, static_cast(Button::DPAD_UP)}, + {HidKeyboardKey_S, static_cast(Button::DPAD_DOWN)}, + {HidKeyboardKey_A, static_cast(Button::DPAD_LEFT)}, + {HidKeyboardKey_D, static_cast(Button::DPAD_RIGHT)}, + + // options (may swap). + {HidKeyboardKey_X, static_cast(Button::Y)}, + {HidKeyboardKey_Z, static_cast(Button::X)}, + + // menus. + {HidKeyboardKey_Q, static_cast(Button::L)}, + {HidKeyboardKey_E, static_cast(Button::R)}, + + // select and back. + {HidKeyboardKey_Return, static_cast(Button::A)}, + {HidKeyboardKey_Space, static_cast(Button::A)}, + {HidKeyboardKey_Backspace, static_cast(Button::B)}, + + // exit. + {HidKeyboardKey_Escape, static_cast(Button::START)}, + + // idk what this should map to. + {HidKeyboardKey_R, static_cast(Button::SELECT)}, +}; + constexpr NszOption NSZ_COMPRESS_LEVEL_OPTIONS[] = { { .value = 0, .name = "Level 0 (no compression)" }, { .value = 1, .name = "Level 1" }, @@ -1026,6 +1057,16 @@ void App::Poll() { hidGetTouchScreenStates(&state, 1); m_touch_info.is_clicked = false; + // todo: + #if 0 + HidMouseState mouse_state{}; + hidGetMouseStates(&mouse_state, 1); + + if (mouse_state.buttons) { + log_write("[MOUSE] buttons: 0x%X x: %d y: %d dx: %d dy: %d wx: %d wy: %d\n", mouse_state.buttons, mouse_state.x, mouse_state.y, mouse_state.delta_x, mouse_state.delta_y, mouse_state.wheel_delta_x, mouse_state.wheel_delta_y); + } + #endif + // todo: replace old touch code with gestures from below #if 0 static HidGestureState prev_gestures[17]{}; @@ -1071,6 +1112,7 @@ void App::Poll() { memcpy(prev_gestures, gestures, sizeof(gestures)); #endif + // todo: support mouse scroll / touch. if (state.count == 1 && !m_touch_info.is_touching) { m_touch_info.initial = m_touch_info.cur = state.touches[0]; m_touch_info.is_touching = true; @@ -1094,14 +1136,29 @@ void App::Poll() { } } + u64 kdown = 0; + u64 kup = 0; + u64 kheld = 0; + // todo: better implement this to match hos if (!m_touch_info.is_touching && !m_touch_info.is_clicked) { + // controller. padUpdate(&m_pad); - m_controller.m_kdown = padGetButtonsDown(&m_pad); - m_controller.m_kheld = padGetButtons(&m_pad); - m_controller.m_kup = padGetButtonsUp(&m_pad); - m_controller.UpdateButtonHeld(static_cast(Button::ANY_DIRECTION), m_delta_time); + kdown |= padGetButtonsDown(&m_pad); + kheld |= padGetButtons(&m_pad); + kup |= padGetButtonsUp(&m_pad); + + // keyboard. + m_keyboard.Update(); + kdown |= m_keyboard.GetButtonsDown(); + kheld |= m_keyboard.GetButtons(); + kup |= m_keyboard.GetButtonsUp(); } + + m_controller.m_kdown = kdown; + m_controller.m_kheld = kheld; + m_controller.m_kup = kup; + m_controller.UpdateButtonHeld(static_cast(Button::ANY_DIRECTION), m_delta_time); } void App::Update() { @@ -1683,9 +1740,14 @@ App::App(const char* argv0) { SCOPED_TIMESTAMP("HID init"); hidInitializeTouchScreen(); hidInitializeGesture(); + hidInitializeKeyboard(); + hidInitializeMouse(); + padConfigureInput(8, HidNpadStyleSet_NpadStandard); // padInitializeDefault(&m_pad); padInitializeAny(&m_pad); + + m_keyboard.Init(KEYBOARD_BUTTON_MAP); } {