feat(service-menu): submenu RESOLUCIO amb canvi en calent de l'offscreen

This commit is contained in:
2026-05-24 12:30:47 +02:00
parent 22827c28fa
commit 7eafe21623
6 changed files with 116 additions and 19 deletions
+80 -11
View File
@@ -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<float>(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(),