diff --git a/source/game/ui/service_menu.cpp b/source/game/ui/service_menu.cpp index 6dfefaa..0327906 100644 --- a/source/game/ui/service_menu.cpp +++ b/source/game/ui/service_menu.cpp @@ -467,6 +467,63 @@ void ServiceMenu::playAcceptSound() { Audio::get()->playSound(UiConfig::get().menuAcceptSound(), Audio::Group::INTERFACE); } +void ServiceMenu::handleGamepadAxisMotion(const SDL_GamepadAxisEvent& ev) { + // Llindars amb histèresi: cal travessar STICK_THRESHOLD per "entrar" a + // un extrem, però només es torna a la zona neutra quan baixa de + // STICK_RELEASE. Així evitem que petits tremolors al voltant del + // llindar facin spam d'events. Valors triats per donar la mateixa + // sensació que el stick del jugador (deadzone=12000) però una mica + // més estrictes per a la navegació discreta del menú. + constexpr Sint16 STICK_THRESHOLD = 16000; + constexpr Sint16 STICK_RELEASE = 8000; + + std::int8_t* zone = nullptr; + bool horizontal = false; + switch (ev.axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + zone = &lx_zone_; + horizontal = true; + break; + case SDL_GAMEPAD_AXIS_LEFTY: + zone = &ly_zone_; + horizontal = false; + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + zone = &rx_zone_; + horizontal = true; + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + zone = &ry_zone_; + horizontal = false; + break; + default: + return; // Ignorem L2/R2 i la resta. + } + + // Càlcul de la zona nova: si el valor està més enllà del llindar fort, + // entra a l'extrem; si està dins de la zona morta, torna a 0; altrament + // manté la zona prèvia (banda d'histèresi). + std::int8_t new_zone = *zone; + if (ev.value > STICK_THRESHOLD) { + new_zone = +1; + } else if (ev.value < -STICK_THRESHOLD) { + new_zone = -1; + } else if (std::abs(ev.value) < STICK_RELEASE) { + new_zone = 0; + } + if (new_zone == *zone) { return; } + *zone = new_zone; + if (new_zone == 0) { return; } // Soltar el stick no dispara res. + + if (horizontal) { + activateCurrent(new_zone); + playAcceptSound(); + } else { + moveCursor(new_zone); + playSelectSound(); + } +} + void ServiceMenu::handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev) { // El botó SERVICE (per defecte BACK; remapejable per dispositiu via // `GamepadBindings`) tanca el menú sencer. Cada mando pot tenir el @@ -570,6 +627,11 @@ auto ServiceMenu::handleEvent(const SDL_Event& ev) -> bool { return true; } + if (ev.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + handleGamepadAxisMotion(ev.gaxis); + return true; + } + if (ev.type != SDL_EVENT_KEY_DOWN) { return false; } const auto SC = ev.key.scancode; diff --git a/source/game/ui/service_menu.hpp b/source/game/ui/service_menu.hpp index 8bd0d22..acb2943 100644 --- a/source/game/ui/service_menu.hpp +++ b/source/game/ui/service_menu.hpp @@ -208,6 +208,12 @@ class ServiceMenu { // sota el llindar de clang-tidy. void handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev); + // Navegació via sticks analògics. Treballa per edge-detection: cada + // eix recorda en quina "zona" està (negativa / neutra / positiva) i + // sols dispara una acció quan creua de neutra a un extrem, igual que + // un botó del DPAD. Aplicada tant al stick esquerre com al dret. + void handleGamepadAxisMotion(const SDL_GamepadAxisEvent& ev); + // Sons d'UI: SELECT acompanya navegació (amunt/avall) i back; ACCEPT // acompanya modificació d'opció (esquerra/dreta/retorn). Els noms de // recurs venen d'`UiConfig` i el preload el fa el Director. @@ -270,6 +276,14 @@ class ServiceMenu { float transition_progress_{0.0F}; // 0 → 1 durant la transició de pàgina. bool capturing_{false}; // True mentre esperem una tecla per a un KEY_BIND. + // Estat per eix del stick per a edge-detection: -1, 0, +1 segons la + // zona on viu el valor actual. Un canvi de zona dispara una acció + // (només quan entra a un extrem); reset a 0 quan torna al centre. + std::int8_t lx_zone_{0}; + std::int8_t ly_zone_{0}; + std::int8_t rx_zone_{0}; + std::int8_t ry_zone_{0}; + // Sub-mòdul de captura de botons del mando. Quan està actiu absorbeix // tots els events de mando (BUTTON_DOWN/UP, AXIS_MOTION) i, en acabar, // persisteix la nova configuració via `GamepadBindings`. Replica el