From 7eafe216237b7697251c48fad67353aa44194dbb Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 24 May 2026 12:30:47 +0200 Subject: [PATCH] feat(service-menu): submenu RESOLUCIO amb canvi en calent de l'offscreen --- data/locale/ca.yaml | 1 + data/locale/en.yaml | 1 + source/core/rendering/sdl_manager.cpp | 20 ++++++ source/core/rendering/sdl_manager.hpp | 16 +++-- source/core/system/service_menu.cpp | 91 +++++++++++++++++++++++---- source/core/system/service_menu.hpp | 6 +- 6 files changed, 116 insertions(+), 19 deletions(-) diff --git a/data/locale/ca.yaml b/data/locale/ca.yaml index 737566c..71ccc66 100644 --- a/data/locale/ca.yaml +++ b/data/locale/ca.yaml @@ -61,6 +61,7 @@ service_menu: video_vsync: "VSYNC" video_aa: "ANTIALIAS" video_postfx: "POSTPROCESSAT" + video_resolution: "RESOLUCIO" # Items del submenu OPCIONS options_language: "IDIOMA" options_show_info: "MOSTRAR INFO" diff --git a/data/locale/en.yaml b/data/locale/en.yaml index 73a4b0b..f5fc757 100644 --- a/data/locale/en.yaml +++ b/data/locale/en.yaml @@ -60,6 +60,7 @@ service_menu: video_vsync: "VSYNC" video_aa: "ANTIALIAS" video_postfx: "POSTPROCESS" + video_resolution: "RESOLUTION" # Items of OPTIONS submenu options_language: "LANGUAGE" options_show_info: "SHOW INFO" diff --git a/source/core/rendering/sdl_manager.cpp b/source/core/rendering/sdl_manager.cpp index 9fddfae..7108e22 100644 --- a/source/core/rendering/sdl_manager.cpp +++ b/source/core/rendering/sdl_manager.cpp @@ -385,6 +385,26 @@ void SDLManager::toggleAntialias() { } } +void SDLManager::setRenderResolution(int w, int h) { + if (!Defaults::Rendering::isValidRenderResolution(w, h)) { + std::cerr << "[SDLManager] Resolucio no valida (" << w << "x" << h + << "), ignorant.\n"; + return; + } + if (w == cfg_->rendering.render_width && h == cfg_->rendering.render_height) { + return; // ja era l'actual + } + if (!gpu_renderer_.resizeRenderTarget(static_cast(w), static_cast(h))) { + std::cerr << "[SDLManager] resizeRenderTarget ha fallat.\n"; + return; + } + cfg_->rendering.render_width = w; + cfg_->rendering.render_height = h; + if (on_persist_) { + on_persist_(); + } +} + void SDLManager::togglePostFx() { const bool NEW_STATE = !gpu_renderer_.isPostFxEnabled(); gpu_renderer_.setPostFxEnabled(NEW_STATE); diff --git a/source/core/rendering/sdl_manager.hpp b/source/core/rendering/sdl_manager.hpp index 239d537..63c0f6f 100644 --- a/source/core/rendering/sdl_manager.hpp +++ b/source/core/rendering/sdl_manager.hpp @@ -30,12 +30,16 @@ class SDLManager { auto operator=(const SDLManager&) -> SDLManager& = delete; // [NUEVO] Gestió de finestra dinàmica - void increaseWindowSize(); // F2: +100px - void decreaseWindowSize(); // F1: -100px - void toggleFullscreen(); // F3 - void toggleVSync(); // F4 - void toggleAntialias(); // F5 - void togglePostFx(); // F6 + void increaseWindowSize(); // F2: +100px + void decreaseWindowSize(); // F1: -100px + void toggleFullscreen(); // F3 + void toggleVSync(); // F4 + void toggleAntialias(); // F5 + void togglePostFx(); // F6 + // Canvia la resolució del render target offscreen (recrea la textura). + // Cal cridar-lo fora d'un frame (event phase, no draw phase). Si el + // valor no es un preset valid o ja es l'actual, es no-op. + void setRenderResolution(int w, int h); auto handleWindowEvent(const SDL_Event& event) -> bool; // Per a SDL_EVENT_WINDOW_RESIZED // Funciones principals (renderizado). diff --git a/source/core/system/service_menu.cpp b/source/core/system/service_menu.cpp index 7f78842..67b026f 100644 --- a/source/core/system/service_menu.cpp +++ b/source/core/system/service_menu.cpp @@ -13,6 +13,7 @@ #include "core/audio/audio.hpp" #include "core/config/engine_config.hpp" #include "core/defaults/audio.hpp" +#include "core/defaults/rendering.hpp" #include "core/defaults/service_menu.hpp" #include "core/locale/locale.hpp" #include "core/rendering/sdl_manager.hpp" @@ -64,6 +65,18 @@ namespace { return result; } + // Resol el text del label d'un item: prioritza label_text (literal) sobre + // label_key (locale). Retorna cadena buida si tots dos son buits. + auto resolveLabel(const System::ServiceMenu::Item& item) -> std::string { + if (!item.label_text.empty()) { + return item.label_text; + } + if (item.label_key.empty()) { + return {}; + } + return Locale::get().text(item.label_key); + } + } // namespace namespace System { @@ -123,6 +136,7 @@ namespace System { return ServiceMenu::Item{ .kind = ServiceMenu::Kind::SUBMENU, .label_key = label_key, + .label_text = {}, .selectable = true, .on_activate = std::move(on_activate), .get_value_text = {}, @@ -145,7 +159,7 @@ namespace System { stack_.push_back(std::move(root)); } - auto ServiceMenu::buildVideoPage() const -> Page { + auto ServiceMenu::buildVideoPage() -> Page { // Helper: localitza ON/OFF per a TOGGLE items. auto on_off_text = [](bool v) -> std::string { return Locale::get().text(v ? "service_menu.value_on" : "service_menu.value_off"); @@ -160,6 +174,7 @@ namespace System { Item{ .kind = Kind::INT_RANGE, .label_key = "service_menu.video_zoom", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [sdl] { return std::format("{:.1f}X", sdl->getScaleFactor()); }, @@ -174,6 +189,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.video_fullscreen", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [sdl, on_off_text] { return on_off_text(sdl->isFullscreen()); }, @@ -183,15 +199,29 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.video_vsync", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [on_off_text] { return on_off_text(ConfigYaml::engine_config.rendering.vsync != 0); }, .on_change = [sdl](int) { sdl->toggleVSync(); }, }, + // RESOLUCIO (sub-submenu amb els 5 presets; mostra l'actual com a valor) + Item{ + .kind = Kind::SUBMENU, + .label_key = "service_menu.video_resolution", + .label_text = {}, + .selectable = true, + .on_activate = [this] { pushPage(buildResolutionPage()); }, + .get_value_text = [] { return std::format("{}X{}", + ConfigYaml::engine_config.rendering.render_width, + ConfigYaml::engine_config.rendering.render_height); }, + .on_change = {}, + }, // ANTIALIAS Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.video_aa", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [on_off_text] { return on_off_text(ConfigYaml::engine_config.rendering.antialias != 0); }, @@ -201,6 +231,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.video_postfx", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [sdl, on_off_text] { return on_off_text(sdl->isPostFxEnabled()); }, @@ -210,6 +241,36 @@ namespace System { return page; } + auto ServiceMenu::buildResolutionPage() const -> Page { + Page page; + page.title_key = "service_menu.video_resolution"; + // El cursor arrenca sobre el preset actual perquè l'usuari vegi quin + // esta seleccionat sense buscar-lo. + const int CURR_W = ConfigYaml::engine_config.rendering.render_width; + const int CURR_H = ConfigYaml::engine_config.rendering.render_height; + std::size_t cursor = 0; + SDLManager* sdl = sdl_; + for (std::size_t i = 0; i < Defaults::Rendering::RESOLUTION_PRESETS.size(); ++i) { + const auto& preset = Defaults::Rendering::RESOLUTION_PRESETS[i]; + if (preset.w == CURR_W && preset.h == CURR_H) { + cursor = i; + } + const int PW = preset.w; + const int PH = preset.h; + page.items.push_back(Item{ + .kind = Kind::ACTION, + .label_key = {}, + .label_text = std::format("{}X{}", PW, PH), + .selectable = true, + .on_activate = [sdl, PW, PH] { sdl->setRenderResolution(PW, PH); }, + .get_value_text = {}, + .on_change = {}, + }); + } + page.cursor = cursor; + return page; + } + auto ServiceMenu::buildAudioPage() -> Page { auto on_off_text = [](bool v) -> std::string { return Locale::get().text(v ? "service_menu.value_on" : "service_menu.value_off"); @@ -229,6 +290,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.audio_master", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [on_off_text] { @@ -243,6 +305,7 @@ namespace System { Item{ .kind = Kind::INT_RANGE, .label_key = "service_menu.audio_master_volume", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [] { @@ -258,6 +321,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.audio_music", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [on_off_text] { @@ -272,6 +336,7 @@ namespace System { Item{ .kind = Kind::INT_RANGE, .label_key = "service_menu.audio_music_volume", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [] { @@ -287,6 +352,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.audio_sound", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [on_off_text] { @@ -301,6 +367,7 @@ namespace System { Item{ .kind = Kind::INT_RANGE, .label_key = "service_menu.audio_sound_volume", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [] { @@ -330,6 +397,7 @@ namespace System { Item{ .kind = Kind::CYCLE, .label_key = "service_menu.options_language", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [] { return Locale::get().text("language." + ConfigYaml::engine_config.locale); }, @@ -344,6 +412,7 @@ namespace System { Item{ .kind = Kind::TOGGLE, .label_key = "service_menu.options_show_info", + .label_text = {}, .selectable = true, .on_activate = {}, .get_value_text = [debug, on_off_text] { return on_off_text(debug != nullptr && debug->isVisible()); }, @@ -369,6 +438,7 @@ namespace System { Item{ .kind = Kind::ACTION, .label_key = "service_menu.system_restart", + .label_text = {}, .selectable = true, .on_activate = [this] { pushConfirmPage("service_menu.confirm_restart", [] { @@ -385,6 +455,7 @@ namespace System { Item{ .kind = Kind::ACTION, .label_key = "service_menu.exit", + .label_text = {}, .selectable = true, .on_activate = [this] { pushConfirmPage("service_menu.confirm_exit", [] { @@ -409,6 +480,7 @@ namespace System { Item{ .kind = Kind::ACTION, .label_key = "service_menu.confirm_no", + .label_text = {}, .selectable = true, .on_activate = [this] { popPage(); }, .get_value_text = {}, @@ -417,6 +489,7 @@ namespace System { Item{ .kind = Kind::ACTION, .label_key = "service_menu.confirm_yes", + .label_text = {}, .selectable = true, .on_activate = std::move(yes_callback), .get_value_text = {}, @@ -572,10 +645,8 @@ namespace System { content_w = std::max(content_w, Graphics::VectorText::getTextWidth(page.subtitle_provider(), SUBTITLE_SCALE, TEXT_SPACING)); } for (const Item& item : page.items) { - const std::string LABEL = item.label_key.empty() - ? std::string{} - : Locale::get().text(item.label_key); - if (item.label_key.empty() && item.get_value_text) { + const std::string LABEL = resolveLabel(item); + if (LABEL.empty() && item.get_value_text) { content_w = std::max(content_w, Graphics::VectorText::getTextWidth(item.get_value_text(), ITEM_SCALE, TEXT_SPACING)); } else if (item.get_value_text) { const float LABEL_W = Graphics::VectorText::getTextWidth(LABEL, ITEM_SCALE, TEXT_SPACING); @@ -810,15 +881,13 @@ namespace System { for (std::size_t i = 0; i < page.items.size(); ++i) { const Item& item = page.items[i]; const SDL_Color COL = (i == page.cursor) ? CURSOR_COLOR : LABEL_COLOR; - // Salta el Locale lookup si label_key esta buit (item nomes-valor). - const std::string LABEL = item.label_key.empty() - ? std::string{} - : Locale::get().text(item.label_key); + // resolveLabel prioritza label_text (literal) sobre label_key (locale). + const std::string LABEL = resolveLabel(item); const float ITEM_TOP = computeItemTopY(BOX_Y, i, HAS_SUBTITLE); const float ITEM_CY = ITEM_TOP + (static_cast(ITEM_HEIGHT) * 0.5F); - if (item.label_key.empty() && item.get_value_text) { - // Item nomes-valor (sense label_key): el text del valor es + if (LABEL.empty() && item.get_value_text) { + // Item nomes-valor (sense label): el text del valor es // renderitza centrat com a label decoratiu. Util per a items // d'informacio com la versio/hash a SISTEMA. text_.renderCentered(item.get_value_text(), diff --git a/source/core/system/service_menu.hpp b/source/core/system/service_menu.hpp index 0b385f9..8b4b732 100644 --- a/source/core/system/service_menu.hpp +++ b/source/core/system/service_menu.hpp @@ -50,7 +50,8 @@ namespace System { struct Item { Kind kind = Kind::LABEL; - std::string label_key; // Clau de locale + 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; @@ -92,7 +93,8 @@ namespace System { ServiceMenu(Rendering::Renderer* renderer, SDLManager* sdl, DebugOverlay* debug_overlay); void buildRootPage(); - [[nodiscard]] auto buildVideoPage() const -> Page; + [[nodiscard]] auto buildVideoPage() -> Page; + [[nodiscard]] auto buildResolutionPage() const -> Page; [[nodiscard]] static auto buildAudioPage() -> Page; [[nodiscard]] auto buildOptionsPage() const -> Page; [[nodiscard]] auto buildSystemPage() -> Page;