Files
aee-arcade/source/core/input/gamepad_manager.hpp
T

141 lines
5.7 KiB
C++

#pragma once
#include <SDL3/SDL.h>
#include <array>
#include <cstddef>
#include <string>
#include <vector>
// Gestor global de gamepads. Descobreix els mandos presents a `init()`
// sense emetre notificacions (el jugador ja els té posats al llançar el
// joc). Durant el runtime, `handleEvent()` processa els events de hot-plug
// i emet notificacions visibles via `Notification::notify`.
//
// El polling real de botons i eixos NO es fa a través d'aquesta classe:
// els consumidors obtenen el `SDL_Gamepad*` primari (o l'enèsim per a
// multi-jugador futur) i el passen directament a
// `SDL_GetGamepadButton`/`SDL_GetGamepadAxis`.
class GamepadManager {
public:
static auto get() -> GamepadManager&;
GamepadManager(const GamepadManager&) = delete;
GamepadManager(GamepadManager&&) = delete;
auto operator=(const GamepadManager&) -> GamepadManager& = delete;
auto operator=(GamepadManager&&) -> GamepadManager& = delete;
// Inicialitza el subsistema SDL de gamepad, intenta carregar un
// `gamecontrollerdb.txt` opcional i obri silenciosament tots els
// mandos ja connectats. Cridat una sola vegada des de Director.
void init();
// Tanca tots els gamepads oberts i treu el subsistema SDL. Cridat
// des de `~Director` abans de `SDL_Quit` per evitar handles orfes.
void shutdown();
// Processa SDL_EVENT_GAMEPAD_ADDED i SDL_EVENT_GAMEPAD_REMOVED
// emesos durant el runtime. Emet notificacions "<nom> connectat" o
// "<nom> desconnectat". El Director decideix quins events li arriben
// via `processGamepadEvents()`.
void handleEvent(const SDL_Event& ev);
// Primary = primer gamepad al vector. `nullptr` si no n'hi ha cap.
// Ús típic: navegació del ServiceMenu. Per al gameplay els jugadors
// usen `getForPlayer(id)`.
[[nodiscard]] auto getPrimary() const -> SDL_Gamepad*;
[[nodiscard]] auto getCount() const -> std::size_t;
// Retorna el mand del jugador `idx`-èsim (0-indexed) en l'ordre de
// connexió. Per al gameplay, `getForPlayer` fa servir l'assignació
// persistida, no l'ordre de connexió.
[[nodiscard]] auto getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad*;
// Informació exposada al menú de servei (llista de mandos
// disponibles). `path` pot estar buit si SDL no l'exposa (p. ex. a
// alguns backends web).
struct Info {
SDL_Gamepad* pad{nullptr};
std::string name;
std::string path;
};
[[nodiscard]] auto getAll() const -> std::vector<Info>;
// Retorna el mand assignat al jugador `player_id` (0 = P1, 1 = P2)
// o `nullptr` si no n'hi ha cap. L'assignació la fixa
// `assignToPlayer` (cridat per Options en carregar `config.yaml` o
// des del ServiceMenu quan l'usuari tria un mando).
[[nodiscard]] auto getForPlayer(int player_id) const -> SDL_Gamepad*;
// Assigna un mando per nom + ruta física. Si ja hi ha un mando
// connectat que coincideix per ruta (o per nom com a fallback), el
// vincula immediatament; si no, l'assignació queda pendent i
// s'aplicarà si el mando es connecta més tard (hot-plug).
// Política last-wins: si l'altre jugador tenia el mateix mando
// vinculat, se'l desvincula silenciosament.
void assignToPlayer(int player_id, const std::string& name, const std::string& path);
// Esborra l'assignació (per si l'usuari trie "Cap" al menú).
void unassignFromPlayer(int player_id);
// Per a cada slot buit, hi vincula el primer mando connectat que no
// estigui reclamat per un altre slot. Equivalent al patró de
// `coffee_crisis_arcade_edition`: les assignacions persistides tenen
// prioritat; els slots que queden buits després s'emplenen per
// ordre. Retorna la llista de parells (player_id, name, path) que
// s'han vinculat, per a que Options pugui persistir-los.
struct AutoAssigned {
int player_id;
std::string name;
std::string path;
};
auto autoAssignFreeSlots() -> std::vector<AutoAssigned>;
private:
struct Entry {
SDL_Gamepad* pad{nullptr};
SDL_JoystickID instance_id{0};
std::string pretty_name;
std::string path;
};
// Assignació persistent per jugador. `name` i `path` venen d'Options
// (o del menú de servei) i poden referenciar un mando encara no
// connectat; en aquest cas `pad` és `nullptr` i el hot-plug el
// vincularà quan aparegui.
struct Assignment {
std::string name;
std::string path;
SDL_Gamepad* pad{nullptr};
};
static constexpr int MAX_PLAYERS = 2;
std::array<Assignment, MAX_PLAYERS> assignments_{};
// Busca a `entries_` el millor match per a una assignació (primer
// per path, després per nom). Retorna l'iterador o `entries_.end()`.
// També accepta assignar `path` nou si ha coincidit per nom
// (cas mando reenchufat a un altre port USB).
void rebindAssignment(Assignment& a);
GamepadManager() = default;
~GamepadManager() = default;
// Retalla `raw` al primer '(' o '[' i elimina espais finals per a
// llevar metadata verbosa ("Retroid Controller (vendor: 1001) ...").
// Fallback "Gamepad" si queda buit. No limita l'amplada final — el
// Notifier multilinia ja gestiona noms llargs.
[[nodiscard]] static auto prettyName(const char* raw) -> std::string;
// Si `base` ja existeix al vector, afegeix " #2", " #3", ... fins que
// trobi un nom lliure. Així dos mandos idèntics es distingeixen a les
// notificacions.
[[nodiscard]] auto uniqueName(const std::string& base) const -> std::string;
void addGamepad(SDL_JoystickID jid, bool notify);
void removeGamepad(SDL_JoystickID jid);
std::vector<Entry> entries_;
bool initialized_{false};
};