217 lines
9.2 KiB
C++
217 lines
9.2 KiB
C++
#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/notification_presets.hpp"
|
|
#include "core/rendering/notifier.hpp"
|
|
#include "core/rendering/render_info.hpp"
|
|
#include "core/rendering/screen.hpp"
|
|
#include "core/rendering/screenshot.hpp"
|
|
#include "core/system/event_buffer.hpp"
|
|
#include "core/ui/ui_config.hpp"
|
|
#include "game/options.hpp"
|
|
#include "game/options_apply.hpp"
|
|
#include "utils/defaults.hpp"
|
|
|
|
namespace GlobalInputs {
|
|
|
|
namespace {
|
|
|
|
// Finestra per a confirmar l'eixida amb una segona pulsació d'Escape.
|
|
// La lliguem a la durada de la notificació EXIT perquè el missatge i
|
|
// la confirmació expiren alhora — si no, l'usuari podria veure el
|
|
// text però ja no ser dins del rang de confirmació (o al revés).
|
|
// El valor concret viu a data/ui.yaml (timings.exit_ms) via UiConfig.
|
|
constexpr int MIN_ZOOM = 1;
|
|
constexpr int MAX_ZOOM = 6;
|
|
|
|
float exit_window_s = 0.0F;
|
|
bool quit_requested = false;
|
|
|
|
void notifyLocale(const std::string& key) {
|
|
Notification::notify(Locale::get().text(key));
|
|
}
|
|
|
|
void onDecZoom() {
|
|
if (Options::window().fullscreen) { return; }
|
|
if (Options::window().zoom <= MIN_ZOOM) { return; }
|
|
const int NEW_ZOOM = Options::window().zoom - 1;
|
|
Options::setWindowZoom(NEW_ZOOM);
|
|
Screen::get().setWindowZoom(NEW_ZOOM);
|
|
Notification::notify(Locale::get().text("input.zoom") + " x" + std::to_string(NEW_ZOOM));
|
|
}
|
|
|
|
void onIncZoom() {
|
|
if (Options::window().fullscreen) { return; }
|
|
if (Options::window().zoom >= MAX_ZOOM) { return; }
|
|
const int NEW_ZOOM = Options::window().zoom + 1;
|
|
Options::setWindowZoom(NEW_ZOOM);
|
|
Screen::get().setWindowZoom(NEW_ZOOM);
|
|
Notification::notify(Locale::get().text("input.zoom") + " x" + std::to_string(NEW_ZOOM));
|
|
}
|
|
|
|
void onFullscreen() {
|
|
const bool NEW_FS = !Options::window().fullscreen;
|
|
Options::setWindowFullscreen(NEW_FS);
|
|
Screen::get().setFullscreen(NEW_FS);
|
|
notifyLocale(NEW_FS ? "input.fullscreen_on" : "input.fullscreen_off");
|
|
}
|
|
|
|
void onToggleVSync() {
|
|
const bool NEW_VSYNC = !Options::video().vsync;
|
|
Options::setVideoVSync(NEW_VSYNC);
|
|
Screen::get().setVSync(NEW_VSYNC);
|
|
notifyLocale(NEW_VSYNC ? "input.vsync_on" : "input.vsync_off");
|
|
}
|
|
|
|
// Volca el canvas ARGB actual a PNG dins de `screenshots/`. Si ha
|
|
// fallat l'escriptura, el handler no mostra notificació (retorn buit).
|
|
void onScreenshot() {
|
|
const auto* buffer = Screen::get().getCanvasArgb();
|
|
const int W = Screen::get().getCanvasWidth();
|
|
const int H = Screen::get().getCanvasHeight();
|
|
const std::string FILENAME = Screenshot::save(buffer, W, H);
|
|
if (!FILENAME.empty()) {
|
|
Notification::notify(Locale::get().text("input.screenshot") + ' ' + FILENAME);
|
|
}
|
|
}
|
|
|
|
void onToggleShader() {
|
|
const bool NEW_ENABLED = !Options::video().shader.enabled;
|
|
Options::setShaderEnabled(NEW_ENABLED);
|
|
OptionsApply::reapplyShader();
|
|
notifyLocale(NEW_ENABLED ? "input.shader_on" : "input.shader_off");
|
|
}
|
|
|
|
void onNextShader() {
|
|
const std::string NEW_NAME = (Options::video().shader.current_shader == "crtpi") ? "postfx" : "crtpi";
|
|
Options::setActiveShaderName(NEW_NAME);
|
|
OptionsApply::reapplyShader();
|
|
Notification::notify(Locale::get().text("input.shader") + ' ' + NEW_NAME);
|
|
}
|
|
|
|
// 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";
|
|
std::string new_preset;
|
|
if (IS_CRTPI) {
|
|
const auto& presets = Options::crtpiPresets();
|
|
if (presets.empty()) { return; }
|
|
auto it = std::ranges::find_if(presets,
|
|
[](const auto& preset) { return preset.name == Options::video().shader.current_crtpi_preset; });
|
|
std::size_t idx = (it == presets.end()) ? 0 : std::distance(presets.begin(), it);
|
|
idx = (idx + 1) % presets.size();
|
|
new_preset = presets[idx].name;
|
|
Options::setCrtPiPreset(new_preset);
|
|
} else {
|
|
const auto& presets = Options::postfxPresets();
|
|
if (presets.empty()) { return; }
|
|
auto it = std::ranges::find_if(presets,
|
|
[](const auto& preset) { return preset.name == Options::video().shader.current_postfx_preset; });
|
|
std::size_t idx = (it == presets.end()) ? 0 : std::distance(presets.begin(), it);
|
|
idx = (idx + 1) % presets.size();
|
|
new_preset = presets[idx].name;
|
|
Options::setPostFXPreset(new_preset);
|
|
}
|
|
OptionsApply::reapplyShader();
|
|
Notification::notify(Locale::get().text("input.preset") + ' ' + new_preset);
|
|
}
|
|
|
|
void onNextScalingMode() {
|
|
using Mode = Defaults::ScalingMode;
|
|
const auto NEW_MODE = static_cast<Mode>((static_cast<int>(Options::video().scaling_mode) + 1) % 5);
|
|
Options::setVideoScalingMode(NEW_MODE);
|
|
Screen::get().setScalingMode(NEW_MODE);
|
|
const char* mode_key = nullptr;
|
|
switch (NEW_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 onCycleRenderInfo() {
|
|
const int NEXT = (static_cast<int>(Options::video().render_info_position) + 1) % Defaults::RENDER_INFO_POSITION_COUNT;
|
|
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
|
|
}
|
|
|
|
// Debug helper: dispara una notificació prou llarga perquè
|
|
// `wrapToWidth` la parteixi en (almenys) dues línies. Útil per a
|
|
// verificar visualment el padding asimètric, el line_gap i la caixa
|
|
// multilinia després de tocar la geometria a `data/ui.yaml`.
|
|
void onDebugLongNotification() {
|
|
Notification::notify("Notificació de prova llarga per a comprovar que el text es parteix correctament en dues línies dins la caixa.");
|
|
}
|
|
|
|
void onExit() {
|
|
if (exit_window_s > 0.0F) {
|
|
quit_requested = true;
|
|
return;
|
|
}
|
|
exit_window_s = static_cast<float>(UiConfig::get().exitMs()) / 1000.0F;
|
|
Notification::notify(Locale::get().text("input.press_again_exit"), Notification::exitPreset());
|
|
}
|
|
|
|
} // 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("screenshot")) {
|
|
onScreenshot();
|
|
} 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")) {
|
|
onCycleRenderInfo();
|
|
} else if (SC == KeyConfig::get().scancode("exit")) {
|
|
onExit();
|
|
} else if (SC == SDL_SCANCODE_KP_2) {
|
|
// Tecla de debug fora del KeyConfig: dispara una notificació
|
|
// multilinia per a inspeccionar el layout de la caixa.
|
|
onDebugLongNotification();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto wantsQuit() -> bool {
|
|
return quit_requested;
|
|
}
|
|
|
|
} // namespace GlobalInputs
|