// define_inputs.hpp - Overlay modal de redefinici de controls (singleton) // © 2026 JailDesigner // // Sub-mòdul inspirat en aee_arcade/source/core/input/define_buttons. Quan el // menú de servei dispara una acció "Redefinir tecles/botons P1/P2", aquest // singleton pren el control: pinta una caixa central, captura events SDL i // avança per una seqüència fixa d'accions, persistint les noves assignacions // a config.yaml en acabar. // // Cicle de vida: // 1. begin(mode, player) → construeix la seqüència (4 passos) i activa // l'overlay. Per a GAMEPAD, retorna false si el jugador no té pad. // 2. handleEvent() captura el següent event vàlid; ESC cancel·la sense // desar; duplicats dins de la sessió es rebutgen silenciosament. // 3. Quan la seqüència es completa, persistim a engine_config + saveToFile, // reapliquem els bindings i mostrem un missatge "OK" durant 1.5 s // abans d'auto-tancar-se. // // El routing d'events es fa des de GlobalEvents::handle: mentre isActive() // retorna true, tots els events SDL es desvien aquí i no arriben al joc ni // al menú de servei. #pragma once #include #include #include #include #include #include "core/graphics/vector_text.hpp" #include "core/input/input_types.hpp" #include "core/rendering/render_context.hpp" namespace System { class DefineInputs { public: enum class Mode : std::uint8_t { KEYBOARD, GAMEPAD }; enum class Player : std::uint8_t { P1, P2 }; static void init(Rendering::Renderer* renderer); static void destroy(); [[nodiscard]] static auto get() -> DefineInputs*; // Comença la sessió. Retorna false per a GAMEPAD si el jugador no té // cap pad assignat (el caller hauria de notificar a l'usuari abans). auto begin(Mode mode, Player player) -> bool; void cancel(); [[nodiscard]] auto isActive() const -> bool; void update(float delta_time); void draw() const; // Retorna true si l'event s'ha consumit (és a dir, mentre l'overlay // és actiu sempre consumeix tot per evitar passages al joc o menú). auto handleEvent(const SDL_Event& event) -> bool; private: explicit DefineInputs(Rendering::Renderer* renderer); enum class Phase : std::uint8_t { INACTIVE, CAPTURING, COMPLETE, // mostra missatge OK breu abans d'auto-cancel }; struct Step { std::string action_label_key; // p.ex. "define.action.left" InputAction action; // mapeig a la struct PlayerBindings int captured{-1}; // scancode o button code; -1 = sense capturar }; void buildSequence(); [[nodiscard]] auto isInUse(int code) const -> bool; void captureAndAdvance(int code); void persistAndComplete(); // Handlers especialitzats segons mode_. auto handleKeyboardEvent(const SDL_Event& event) -> bool; auto handleGamepadEvent(const SDL_Event& event) -> bool; // Edge-detect per als triggers L2/R2 com a botons virtuals. void processTrigger(int virtual_button, bool& was_pressed, bool now); Rendering::Renderer* renderer_; Graphics::VectorText text_; Phase phase_{Phase::INACTIVE}; Mode mode_{Mode::KEYBOARD}; Player player_{Player::P1}; std::vector sequence_; std::size_t index_{0}; float complete_timer_s_{0.0F}; // Estat d'edge-detect dels triggers durant la sessió GAMEPAD. bool l2_was_pressed_{false}; bool r2_was_pressed_{false}; static std::unique_ptr instance; }; } // namespace System