From a4b567588ffadb310dc5689c63df50dc4aa77b91 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 24 May 2026 20:42:33 +0200 Subject: [PATCH] feat(service_menu): navegacio amb mando (dpad, stick, fire = enter, accelerate = back) ServiceMenu::handleEvent ara accepta tambe SDL_EVENT_GAMEPAD_BUTTON_DOWN i SDL_EVENT_GAMEPAD_AXIS_MOTION. Mapeig: dpad UP/DOWN/LEFT/RIGHT mouen el cursor, el boto FIRE configurat per qualsevol jugador equival a ENTER (activa l'item), ACCELERATE equival a BACK (popPage). El stick esquerre fa nav amb edge-detect: cal tornar a centre per disparar una altra entrada. GlobalEvents::forwardToServiceMenu envia tots aquests events al menu quan esta obert. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/system/global_events.cpp | 33 ++++---- source/core/system/service_menu.cpp | 108 ++++++++++++++++++++++++++- source/core/system/service_menu.hpp | 22 +++++- 3 files changed, 146 insertions(+), 17 deletions(-) diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp index 2f8e6f5..ad4e2df 100644 --- a/source/core/system/global_events.cpp +++ b/source/core/system/global_events.cpp @@ -23,25 +23,32 @@ namespace GlobalEvents { namespace { - // Reenvia el KEY_DOWN al menu de servei si esta obert i la tecla no - // es F1-F12 ni ESC (que sempre passen com a globals). Retorna true si - // el menu l'ha consumit. + // Reenvia events al menu de servei si esta obert. Accepta: + // - KEY_DOWN (excepte F1-F12 i ESC, que sempre passen com a globals) + // - GAMEPAD_BUTTON_DOWN (per navegacio amb dpad + FIRE/ACCELERATE) + // - GAMEPAD_AXIS_MOTION (per navegacio amb stick) + // Retorna true si l'event s'ha entregat al menu. auto forwardToServiceMenu(const SDL_Event& event) -> bool { - if (event.type != SDL_EVENT_KEY_DOWN) { - return false; - } auto* menu = System::ServiceMenu::get(); if (menu == nullptr || !menu->isOpen()) { return false; } - const SDL_Scancode SC = event.key.scancode; - const bool PASSTHROUGH = (SC == SDL_SCANCODE_ESCAPE) || - (SC >= SDL_SCANCODE_F1 && SC <= SDL_SCANCODE_F12); - if (PASSTHROUGH) { - return false; + if (event.type == SDL_EVENT_KEY_DOWN) { + const SDL_Scancode SC = event.key.scancode; + const bool PASSTHROUGH = (SC == SDL_SCANCODE_ESCAPE) || + (SC >= SDL_SCANCODE_F1 && SC <= SDL_SCANCODE_F12); + if (PASSTHROUGH) { + return false; + } + menu->handleEvent(event); + return true; } - menu->handleEvent(event); - return true; + if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || + event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + menu->handleEvent(event); + return true; + } + return false; } // Si l'overlay de redefinicio esta actiu, engoleix tots els events. diff --git a/source/core/system/service_menu.cpp b/source/core/system/service_menu.cpp index 45c63f8..b3b520b 100644 --- a/source/core/system/service_menu.cpp +++ b/source/core/system/service_menu.cpp @@ -718,10 +718,52 @@ namespace System { } } - auto ServiceMenu::handleEvent(const SDL_Event& event) -> bool { - if (!open_ || stack_.empty() || event.type != SDL_EVENT_KEY_DOWN) { + namespace { + + // Llindar de stick per a navegacio de menu (mig camp del rang ±32767). + // Mes baix que el del joc (30000) per a una resposta mes agil al menu. + constexpr Sint16 MENU_STICK_THRESHOLD = 16384; + + // Retorna true si el codi de boto SDL coincideix amb l'accio + // configurada per algun dels dos jugadors (es a dir, el boto te el + // mateix codi al binding de FIRE o ACCELERATE del pad emissor). + auto buttonMatchesAction(SDL_JoystickID which, int button, InputAction action) -> bool { + const auto* input = Input::get(); + if (input == nullptr) { + return false; + } + for (int i = 0; i < 2; ++i) { + auto pad = input->getPlayerGamepad(i); + if (!pad || pad->instance_id != which) { + continue; + } + auto it = pad->bindings.find(action); + if (it != pad->bindings.end() && it->second.button == button) { + return true; + } + } return false; } + + } // namespace + + auto ServiceMenu::handleEvent(const SDL_Event& event) -> bool { + if (!open_ || stack_.empty()) { + return false; + } + if (event.type == SDL_EVENT_KEY_DOWN) { + return handleKeyDown(event); + } + if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + return handleGamepadButton(event); + } + if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + return handleGamepadAxis(event); + } + return false; + } + + auto ServiceMenu::handleKeyDown(const SDL_Event& event) -> bool { switch (event.key.scancode) { case SDL_SCANCODE_UP: moveCursor(-1); @@ -747,6 +789,68 @@ namespace System { } } + auto ServiceMenu::handleGamepadButton(const SDL_Event& event) -> bool { + const int BTN = static_cast(event.gbutton.button); + if (BTN == SDL_GAMEPAD_BUTTON_DPAD_UP) { + moveCursor(-1); + return true; + } + if (BTN == SDL_GAMEPAD_BUTTON_DPAD_DOWN) { + moveCursor(+1); + return true; + } + if (BTN == SDL_GAMEPAD_BUTTON_DPAD_LEFT) { + changeValue(-1); + return true; + } + if (BTN == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) { + changeValue(+1); + return true; + } + // Botons d'accio per al pad emissor: FIRE = ENTER, ACCELERATE = BACK. + if (buttonMatchesAction(event.gbutton.which, BTN, InputAction::SHOOT)) { + activateCurrent(); + return true; + } + if (buttonMatchesAction(event.gbutton.which, BTN, InputAction::THRUST)) { + popPage(); + return true; + } + return false; + } + + auto ServiceMenu::handleGamepadAxis(const SDL_Event& event) -> bool { + const auto AXIS = static_cast(event.gaxis.axis); + const Sint16 VAL = event.gaxis.value; + if (AXIS == SDL_GAMEPAD_AXIS_LEFTX) { + const bool LEFT_NOW = VAL < -MENU_STICK_THRESHOLD; + const bool RIGHT_NOW = VAL > MENU_STICK_THRESHOLD; + if (LEFT_NOW && !stick_left_held_) { + changeValue(-1); + } + if (RIGHT_NOW && !stick_right_held_) { + changeValue(+1); + } + stick_left_held_ = LEFT_NOW; + stick_right_held_ = RIGHT_NOW; + return true; + } + if (AXIS == SDL_GAMEPAD_AXIS_LEFTY) { + const bool UP_NOW = VAL < -MENU_STICK_THRESHOLD; + const bool DOWN_NOW = VAL > MENU_STICK_THRESHOLD; + if (UP_NOW && !stick_up_held_) { + moveCursor(-1); + } + if (DOWN_NOW && !stick_down_held_) { + moveCursor(+1); + } + stick_up_held_ = UP_NOW; + stick_down_held_ = DOWN_NOW; + return true; + } + return false; + } + auto ServiceMenu::computeTargetHeight() const -> float { if (stack_.empty()) { return 0.0F; diff --git a/source/core/system/service_menu.hpp b/source/core/system/service_menu.hpp index 0fa1538..fc99612 100644 --- a/source/core/system/service_menu.hpp +++ b/source/core/system/service_menu.hpp @@ -85,13 +85,22 @@ namespace System { void update(float delta_time); void draw() const; - // Processa el KEY_DOWN. Retorna true si l'ha consumit (UP/DOWN/ENTER/ - // RIGHT/BACKSPACE/LEFT mentre esta obert). false en qualsevol altre cas. + // Processa events de navegacio. Retorna true si l'event s'ha consumit. + // Accepta: + // - SDL_EVENT_KEY_DOWN: UP/DOWN/ENTER/RIGHT/LEFT/BACKSPACE. + // - SDL_EVENT_GAMEPAD_BUTTON_DOWN: DPAD per nav, FIRE = ENTER, + // ACCELERATE = BACK. La resta de botons s'ignoren. + // - SDL_EVENT_GAMEPAD_AXIS_MOTION: stick X/Y amb edge-detect. auto handleEvent(const SDL_Event& event) -> bool; private: ServiceMenu(Rendering::Renderer* renderer, SDLManager* sdl, DebugOverlay* debug_overlay); + // Sub-handlers de handleEvent. Privats, no son part de l'API publica. + auto handleKeyDown(const SDL_Event& event) -> bool; + auto handleGamepadButton(const SDL_Event& event) -> bool; + auto handleGamepadAxis(const SDL_Event& event) -> bool; + void buildRootPage(); [[nodiscard]] auto buildVideoPage() -> Page; [[nodiscard]] auto buildResolutionPage() const -> Page; @@ -140,6 +149,15 @@ namespace System { float highlight_h_ = 0.0F; bool highlight_snap_ = true; + // Edge-detect de stick analogic per a navegacio. Una sola activacio + // per direccio: cal tornar a centre (sota el llindar) per disparar + // una altra. Compartit entre tots els pads — qualsevol jugador pot + // navegar el menu. + bool stick_left_held_ = false; + bool stick_right_held_ = false; + bool stick_up_held_ = false; + bool stick_down_held_ = false; + static std::unique_ptr instance; };