// service_menu.hpp - Menu de servei (singleton) // © 2026 JailDesigner // // Overlay de configuracio global accessible amb F12 des de qualsevol escena // (LOGO, TITLE, GAME). Captura tots els KEY_DOWN excepte F1-F12 i ESC, que // continuen arribant a GlobalEvents. Mentre esta obert, GameScene::update() // fa early return per pausar el joc; LOGO i TITLE continuen renderitzant-se // sota el menu. // // Arquitectura inspirada en aee_arcade service_menu.{hpp,cpp}: pila de // pagines amb cursor, animacio open/close amb easing easeOutQuad i clipping // del contingut mentre la caixa creix/decreix. // // API singleton equivalent a Notifier: init() al startup amb un renderer, // get() retorna el punter, destroy() al teardown. #pragma once #include #include #include #include #include #include #include #include "core/graphics/vector_text.hpp" #include "core/rendering/render_context.hpp" class SDLManager; namespace System { class DebugOverlay; class ServiceMenu { public: // Tipus d'item de menu. En aquesta iteracio nomes s'usen SUBMENU i // LABEL; la resta queden reservats per a iteracions futures (toggles // de vsync/zoom, picker d'idioma, restart, exit...). enum class Kind : std::uint8_t { LABEL, // No interactiu, nomes es dibuixa TOGGLE, // bool flip — reservat CYCLE, // index amb modul — reservat INT_RANGE, // step ± — reservat SUBMENU, // pushPage en activar — usat ACTION // call al lambda en activar — reservat }; struct Item { Kind kind = Kind::LABEL; std::string label_key; // Clau de locale (s'ignora si label_text no esta buit) std::string label_text; // Text literal (no locale). Util per a labels que no necessiten traduccio (resolucions, etc.) bool selectable = true; // SUBMENU / ACTION: callback en ENTER / RIGHT. std::function on_activate; // TOGGLE / CYCLE / INT_RANGE: text del valor actual (renderitzat a la dreta). std::function get_value_text; // TOGGLE / CYCLE / INT_RANGE: callback amb +1 (RIGHT/ENTER) o -1 (LEFT). std::function on_change; }; struct Page { std::string title_key; // Subtitol opcional, renderitzat sota el titol amb tipografia mes // petita i color apagat. Es una funcio perque pot ser dinamic // (versio+hash, etc.). Si esta buit, no es renderitza. std::function subtitle_provider; std::vector items; std::size_t cursor = 0; }; // Inicialitza el singleton amb el renderer global, l'SDLManager (video // toggles: fullscreen, vsync, AA, postfx, zoom) i el DebugOverlay // (toggle del HUD de debug a OPCIONS). Tots propietat del Director. static void init(Rendering::Renderer* renderer, SDLManager* sdl, DebugOverlay* debug_overlay); static void destroy(); [[nodiscard]] static auto get() -> ServiceMenu*; // F12: alterna obrir/tancar amb animacio. void toggle(); [[nodiscard]] auto isOpen() const -> bool; void update(float delta_time); void draw() const; // Processa el KEY_DOWN. Retorna true si l'ha consumit (UP/DOWN/ENTER/ // RIGHT/BACKSPACE/LEFT mentre esta obert). false en qualsevol altre cas. auto handleEvent(const SDL_Event& event) -> bool; private: ServiceMenu(Rendering::Renderer* renderer, SDLManager* sdl, DebugOverlay* debug_overlay); void buildRootPage(); [[nodiscard]] auto buildVideoPage() -> Page; [[nodiscard]] auto buildResolutionPage() const -> Page; [[nodiscard]] static auto buildAudioPage() -> Page; [[nodiscard]] auto buildOptionsPage() const -> Page; [[nodiscard]] auto buildSystemPage() -> Page; [[nodiscard]] static auto buildControlsPage() -> Page; // Pagina de confirmacio "ESTAS SEGUR? NO/SI". on_yes s'executa si // l'usuari selecciona SI; el cursor per defecte apunta a NO. void pushConfirmPage(const std::string& title_key, std::function on_yes); void pushPage(Page page); void popPage(); void moveCursor(int direction); void activateCurrent(); // RIGHT (direction=+1) / LEFT (direction=-1). Per a TOGGLE/CYCLE/INT_RANGE // crida on_change. Per a SUBMENU/ACTION nomes +1 (entra/activa). void changeValue(int direction); // Alçada objectiu de la caixa per a la pagina superior (sense animacio). [[nodiscard]] auto computeTargetHeight() const -> float; // Ample objectiu de la caixa per a la pagina superior (sense animacio). // Pren com a base BOX_WIDTH_MIN i s'eixampla si algun text no hi cap. [[nodiscard]] auto computeTargetWidth() const -> float; // Y (top) de l'item index dins una caixa col·locada a box_y. Si la // pagina te subtitol, els items es desplacen cap avall. [[nodiscard]] static auto computeItemTopY(float box_y, std::size_t index, bool has_subtitle) -> float; Rendering::Renderer* renderer_; SDLManager* sdl_; DebugOverlay* debug_overlay_; Graphics::VectorText text_; std::vector stack_; bool open_ = false; bool closing_ = false; float open_anim_ = 0.0F; // 0..1 raw (sense easing) float animated_h_ = 0.0F; // Alçada animada amb smoothing exponencial float animated_w_ = 0.0F; // Ample animat (eixampla segons contingut) // Estat del highlight (rectangle del cursor). Es lerpa cap a l'item // actiu amb ease-out exponencial; quan el cursor "salta" (open o // push/pop de pagina), s'enganxa directament al nou objectiu. float highlight_y_ = 0.0F; float highlight_h_ = 0.0F; bool highlight_snap_ = true; static std::unique_ptr instance; }; } // namespace System