129 lines
5.2 KiB
C++
129 lines
5.2 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 a `InputState::getGamepadButton/Axis`,
|
|
// que ho filtra pel flag de bloqueig de la UI (mateixa estratègia que
|
|
// amb el teclat).
|
|
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);
|
|
|
|
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};
|
|
};
|