GlobalInputs F1-F12 + Escape, Screen sense destruir finestra, escalat al GPU, presets i fons canvas (v0.11.0)

This commit is contained in:
JailDesigner committed 2026-04-19 14:27:16 +02:00
1 parent 837eb73398
commit 297dc85e47
23 files changed
+653 -34

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
#include "core/input/global_inputs.hpp"
#include <SDL3/SDL.h>
#include <algorithm>
#include <string>
#include "core/input/key_config.hpp"
#include "core/locale/locale.hpp"
#include "core/rendering/notifier.hpp"
#include "core/rendering/render_info.hpp"
#include "core/rendering/screen.hpp"
#include "core/system/event_buffer.hpp"
#include "game/options.hpp"
#include "utils/defaults.hpp"
namespace GlobalInputs {
namespace {
// Finestra per a confirmar l'eixida amb una segona pulsació d'Escape.
constexpr float EXIT_WINDOW_S = 1.5F;
constexpr int MIN_ZOOM = 1;
constexpr int MAX_ZOOM = 6;
float exit_window_s = 0.0F;
bool quit_requested = false;
// Canvia uniforms i shader actiu sense recrear la finestra.
void reapplyShader() {
Screen::get().setActiveShader(Options::buildActiveShader());
Screen::get().applyPostFXParams(Options::buildPostFXParams());
Screen::get().applyCrtPiParams(Options::buildCrtPiParams());
}
void notifyLocale(const std::string& key) {
Notifier::get().notify(Locale::get().text(key));
}
void onDecZoom() {
if (Options::window.fullscreen) { return; }
if (Options::window.zoom <= MIN_ZOOM) { return; }
Options::window.zoom--;
Screen::get().setWindowZoom(Options::window.zoom);
Options::saveToFile();
Notifier::get().notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
}
void onIncZoom() {
if (Options::window.fullscreen) { return; }
if (Options::window.zoom >= MAX_ZOOM) { return; }
Options::window.zoom++;
Screen::get().setWindowZoom(Options::window.zoom);
Options::saveToFile();
Notifier::get().notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
}
void onFullscreen() {
Options::window.fullscreen = !Options::window.fullscreen;
Screen::get().setFullscreen(Options::window.fullscreen);
Options::saveToFile();
notifyLocale(Options::window.fullscreen ? "input.fullscreen_on" : "input.fullscreen_off");
}
void onToggleVSync() {
Options::video.vsync = !Options::video.vsync;
Screen::get().setVSync(Options::video.vsync);
Options::saveToFile();
notifyLocale(Options::video.vsync ? "input.vsync_on" : "input.vsync_off");
}
void onToggleShader() {
Options::video.shader.enabled = !Options::video.shader.enabled;
reapplyShader();
Options::saveToFile();
notifyLocale(Options::video.shader.enabled ? "input.shader_on" : "input.shader_off");
}
void onNextShader() {
Options::video.shader.current_shader = (Options::video.shader.current_shader == "crtpi") ? "postfx" : "crtpi";
reapplyShader();
Options::saveToFile();
Notifier::get().notify(Locale::get().text("input.shader") + ": " + Options::video.shader.current_shader);
}
// Busca l'entrada actual al vector de presets i passa a la següent
// (wrap-around). Si no en troba cap s'evita el canvi silenciosament.
void onNextPreset() {
const bool IS_CRTPI = Options::video.shader.current_shader == "crtpi";
if (IS_CRTPI) {
if (Options::crtpi_presets.empty()) { return; }
auto it = std::ranges::find_if(Options::crtpi_presets,
[](const auto& preset) { return preset.name == Options::video.shader.current_crtpi_preset; });
std::size_t idx = (it == Options::crtpi_presets.end()) ? 0 : std::distance(Options::crtpi_presets.begin(), it);
idx = (idx + 1) % Options::crtpi_presets.size();
Options::video.shader.current_crtpi_preset = Options::crtpi_presets[idx].name;
} else {
if (Options::postfx_presets.empty()) { return; }
auto it = std::ranges::find_if(Options::postfx_presets,
[](const auto& preset) { return preset.name == Options::video.shader.current_postfx_preset; });
std::size_t idx = (it == Options::postfx_presets.end()) ? 0 : std::distance(Options::postfx_presets.begin(), it);
idx = (idx + 1) % Options::postfx_presets.size();
Options::video.shader.current_postfx_preset = Options::postfx_presets[idx].name;
}
reapplyShader();
Options::saveToFile();
const std::string& name = IS_CRTPI ? Options::video.shader.current_crtpi_preset : Options::video.shader.current_postfx_preset;
Notifier::get().notify(Locale::get().text("input.preset") + ": " + name);
}
void onNextScalingMode() {
using Mode = Defaults::ScalingMode;
Options::video.scaling_mode = static_cast<Mode>((static_cast<int>(Options::video.scaling_mode) + 1) % 5);
Screen::get().setScalingMode(Options::video.scaling_mode);
Options::saveToFile();
const char* mode_key = nullptr;
switch (Options::video.scaling_mode) {
case Mode::DISABLED:
mode_key = "input.scaling_disabled";
break;
case Mode::STRETCH:
mode_key = "input.scaling_stretch";
break;
case Mode::LETTERBOX:
mode_key = "input.scaling_letterbox";
break;
case Mode::OVERSCAN:
mode_key = "input.scaling_overscan";
break;
case Mode::INTEGER:
mode_key = "input.scaling_integer";
break;
}
notifyLocale(mode_key);
}
void onToggleRenderInfo() {
RenderInfo::get().toggle();
}
void onExit() {
if (exit_window_s > 0.0F) {
quit_requested = true;
return;
}
exit_window_s = EXIT_WINDOW_S;
// La notificació dura el doble de la finestra; si l'usuari no
// prem una segona volta abans del timeout, es desarmarà sol.
Notifier::get().notify(Locale::get().text("input.press_again_exit"),
Notification::DEFAULT.text_color,
Notification::DEFAULT.bg_color,
static_cast<int>(EXIT_WINDOW_S * 1000.0F));
}
} // namespace
void update(int delta_ms) {
exit_window_s = std::max(0.0F, exit_window_s - (static_cast<float>(delta_ms) / 1000.0F));
for (const auto& event : EventBuffer::events) {
if (event.type != SDL_EVENT_KEY_DOWN) { continue; }
const auto SC = event.key.scancode;
if (SC == KeyConfig::get().scancode("dec_zoom")) {
onDecZoom();
} else if (SC == KeyConfig::get().scancode("inc_zoom")) {
onIncZoom();
} else if (SC == KeyConfig::get().scancode("fullscreen")) {
onFullscreen();
} else if (SC == KeyConfig::get().scancode("toggle_shader")) {
onToggleShader();
} else if (SC == KeyConfig::get().scancode("toggle_vsync")) {
onToggleVSync();
} else if (SC == KeyConfig::get().scancode("next_shader")) {
onNextShader();
} else if (SC == KeyConfig::get().scancode("next_preset")) {
onNextPreset();
} else if (SC == KeyConfig::get().scancode("next_scaling_mode")) {
onNextScalingMode();
} else if (SC == KeyConfig::get().scancode("toggle_render_info")) {
onToggleRenderInfo();
} else if (SC == KeyConfig::get().scancode("exit")) {
onExit();
}
}
}
auto wantsQuit() -> bool {
return quit_requested;
}
} // namespace GlobalInputs
+14
View File
@@ -0,0 +1,14 @@
#pragma once
// Hotkeys globals d'UI (F1–F12 + Escape). Consumides al principi del update
// de cada escena, així les tecles de sistema mai es barregen amb l'input del
// joc. L'eixida amb Escape és en dos passos: la primera pulsació mostra una
// notificació de confirmació i arma un temporitzador intern; la segona dins
// de la finestra activa wantsQuit().
namespace GlobalInputs {
void update(int delta_ms);
[[nodiscard]] auto wantsQuit() -> bool;
} // namespace GlobalInputs
+48
View File
@@ -0,0 +1,48 @@
#include "core/input/key_config.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include "external/fkyaml_node.hpp"
auto KeyConfig::get() -> KeyConfig& {
static KeyConfig instance_;
return instance_;
}
void KeyConfig::load(const std::string& file_path) {
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "KeyConfig: no s'ha pogut obrir " << file_path << '\n';
return;
}
try {
auto yaml = fkyaml::node::deserialize(file);
if (!yaml.contains("keys") || !yaml["keys"].is_sequence()) {
std::cerr << "KeyConfig: falta la secció 'keys' a " << file_path << '\n';
return;
}
bindings_.clear();
for (const auto& entry : yaml["keys"]) {
if (!entry.contains("id") || !entry.contains("code")) { continue; }
const auto ID = entry["id"].get_value<std::string>();
const auto CODE = entry["code"].get_value<std::string>();
const SDL_Scancode SC = SDL_GetScancodeFromName(CODE.c_str());
if (SC == SDL_SCANCODE_UNKNOWN) {
std::cerr << "KeyConfig: codi SDL desconegut '" << CODE << "' per id '" << ID << "'\n";
continue;
}
bindings_[ID] = SC;
}
std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << file_path << '\n';
} catch (const std::exception& e) {
std::cerr << "KeyConfig: error parsejant " << file_path << ": " << e.what() << '\n';
}
}
auto KeyConfig::scancode(const std::string& id) const -> SDL_Scancode {
auto it = bindings_.find(id);
return it != bindings_.end() ? it->second : SDL_SCANCODE_UNKNOWN;
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <unordered_map>
// Mapeig id → SDL_Scancode carregat des de data/input/keys.yaml. Consumit
// per GlobalInputs i (en el futur) per menús de remapat. No té cap cost
// global: si la clau no existeix, retorna SDL_SCANCODE_UNKNOWN i GlobalInputs
// ignora l'acció. D'aquesta manera podem afegir nous bindings al YAML sense
// tocar codi mentre els handlers continuen funcionant.
class KeyConfig {
public:
static auto get() -> KeyConfig&;
KeyConfig(const KeyConfig&) = delete;
KeyConfig(KeyConfig&&) = delete;
auto operator=(const KeyConfig&) -> KeyConfig& = delete;
auto operator=(KeyConfig&&) -> KeyConfig& = delete;
void load(const std::string& file_path);
[[nodiscard]] auto scancode(const std::string& id) const -> SDL_Scancode;
private:
KeyConfig() = default;
~KeyConfig() = default;
std::unordered_map<std::string, SDL_Scancode> bindings_;
};