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) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 20:42:33 +02:00
parent 2e74fea2d5
commit a4b567588f
3 changed files with 146 additions and 17 deletions
+106 -2
View File
@@ -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<int>(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<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;
}
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;