feat(service-menu): submenu RESOLUCIO amb canvi en calent de l'offscreen
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<float>(w), static_cast<float>(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);
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<void()> 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;
|
||||
|
||||
Reference in New Issue
Block a user