feat(service_menu): triggers L2/R2 navegables + so al rebind

El menu de servei nomes processava AXIS_MOTION dels sticks i descartava
els triggers. Com SDL3 mai emet button events per a L2/R2 (nomes axis),
rebindar FIRE o ACCEL a un trigger feia que no funcionaren al menu, fins
i tot estant correctament al joc per via del poll de Input::checkTriggerInput.
Afegim edge-detect dels dos triggers al handleGamepadAxis i, quan creuen
el llindar, mirem si el codi virtual (100=L2, 101=R2) coincideix amb el
binding de FIRE → activateCurrent, o ACCEL → popPage. Estat held per
trigger per evitar repeticions mentre es mante premut.

DefineInputs ara reprodueix el so accept del menu en cada captura
valida, que estava silent i no donava feedback al rebind.

Tambe extraiem processStickX/Y i processTriggerEdge per mantenir
handleGamepadAxis com a dispatcher i sota el llindar de complexitat
cognitiva del clang-tidy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 22:38:10 +02:00
parent daa7eaf811
commit cefafe99e4
3 changed files with 76 additions and 27 deletions
+62 -26
View File
@@ -818,6 +818,9 @@ namespace System {
// 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;
// Llindar de trigger (mateix valor que MENU_TRIGGER_THRESHOLD, que
// és private). Edge a partir del 50% del rang.
constexpr Sint16 MENU_TRIGGER_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
@@ -914,36 +917,69 @@ namespace System {
return false;
}
void ServiceMenu::processStickX(Sint16 val) {
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;
}
void ServiceMenu::processStickY(Sint16 val) {
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;
}
// Edge-detect d'un trigger: si creua el llindar amunt, despatxa
// ENTER/BACK segons el binding (FIRE/ACCEL) que apunta al codi
// virtual del trigger (100 = L2, 101 = R2).
void ServiceMenu::processTriggerEdge(SDL_JoystickID which, Sint16 val, int virtual_button, bool& held) {
const bool NOW = val > MENU_TRIGGER_THRESHOLD;
if (NOW && !held) {
if (buttonMatchesAction(which, virtual_button, InputAction::SHOOT)) {
activateCurrent();
} else if (buttonMatchesAction(which, virtual_button, InputAction::THRUST)) {
popPage();
}
}
held = NOW;
}
auto ServiceMenu::handleGamepadAxis(const SDL_Event& event) -> bool {
const auto AXIS = static_cast<SDL_GamepadAxis>(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;
switch (AXIS) {
case SDL_GAMEPAD_AXIS_LEFTX:
processStickX(VAL);
return true;
case SDL_GAMEPAD_AXIS_LEFTY:
processStickY(VAL);
return true;
// Triggers L2/R2: SDL3 nomes emet AXIS_MOTION, no button events.
// Per poder rebindar FIRE/ACCEL als triggers, sintetitzem aqui
// la pulsacio amb edge-detect i la passem pel mateix flux.
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
processTriggerEdge(event.gaxis.which, VAL, Input::TRIGGER_L2_AS_BUTTON, trigger_l2_held_);
return true;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
processTriggerEdge(event.gaxis.which, VAL, Input::TRIGGER_R2_AS_BUTTON, trigger_r2_held_);
return true;
default:
return false;
}
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 {
+10
View File
@@ -100,6 +100,11 @@ namespace System {
auto handleKeyDown(const SDL_Event& event) -> bool;
auto handleGamepadButton(const SDL_Event& event) -> bool;
auto handleGamepadAxis(const SDL_Event& event) -> bool;
// Helpers per a cada eix; permeten que handleGamepadAxis es quedi
// com a dispatcher i no bote el llindar de complexitat.
void processStickX(Sint16 val);
void processStickY(Sint16 val);
void processTriggerEdge(SDL_JoystickID which, Sint16 val, int virtual_button, bool& held);
void buildRootPage();
[[nodiscard]] auto buildVideoPage() -> Page;
@@ -162,6 +167,11 @@ namespace System {
bool stick_right_held_ = false;
bool stick_up_held_ = false;
bool stick_down_held_ = false;
// Edge-detect dels triggers L2/R2 com a botons virtuals. SDL3 no
// emet button events per als triggers; els llegim com a axis i
// sintetitzem una pulsacio quan creuen el llindar.
bool trigger_l2_held_ = false;
bool trigger_r2_held_ = false;
static std::unique_ptr<ServiceMenu> instance;
};