ESC global amb doble pulsació: F12=pausa, BACKSPACE=cancel, text pausa més clar

This commit is contained in:
2026-05-17 18:10:15 +02:00
parent 659e37e5a1
commit a40931c7ca
12 changed files with 104 additions and 66 deletions
+29
View File
@@ -17,13 +17,24 @@ namespace GlobalInputs {
constexpr int LANG_WINDOW = 98;
constexpr int LANG_SHADER = 99;
constexpr int LANG_PRESET = 100;
constexpr int LANG_EXIT_CONFIRM = 101;
constexpr Uint32 NOTIFY_MS = 1500;
constexpr Uint32 EXIT_CONFIRM_MS = 2000;
const Color BLACK = {0x00, 0x00, 0x00};
const Color CYAN = {0x00, 0xFF, 0xFF};
const Color YELLOW = {0xFF, 0xE0, 0x40};
const Color MAGENTA = {0xFF, 0x00, 0xFF};
const Color GREEN = {0x00, 0xFF, 0x80};
const Color RED = {0xFF, 0x40, 0x40};
// Patró de doble pulsació: la primera pulsació d'EXIT mostra una
// notificació en vermell i obre una finestra de confirmació; una
// segona pulsació dins la finestra activa `quit_requested`. La
// finestra coincideix amb la durada del missatge perquè usuari i
// sistema sempre estiguin sincronitzats.
Uint32 exit_window_until_ticks = 0;
bool quit_requested = false;
void notifyZoom() {
const std::string MSG = Lang::get()->getText(LANG_ZOOM) + " " + std::to_string(Options::window.zoom) + "x";
@@ -51,11 +62,25 @@ namespace GlobalInputs {
const std::string MSG = Lang::get()->getText(LANG_PRESET) + " " + Screen::get()->getCurrentPresetName();
Screen::get()->notify(MSG, GREEN, BLACK, NOTIFY_MS);
}
void onExit() {
const Uint32 NOW = SDL_GetTicks();
if (NOW < exit_window_until_ticks) {
quit_requested = true;
return;
}
exit_window_until_ticks = NOW + EXIT_CONFIRM_MS;
Screen::get()->notify(Lang::get()->getText(LANG_EXIT_CONFIRM), RED, BLACK, EXIT_CONFIRM_MS);
}
} // namespace
auto handle() -> bool {
if (Screen::get() == nullptr || Input::get() == nullptr) { return false; }
if (Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF)) {
onExit();
return true;
}
if (Input::get()->checkInput(Input::Action::WINDOW_FULLSCREEN, Input::Repeat::OFF)) {
Screen::get()->toggleVideoMode();
notifyFullscreen();
@@ -95,4 +120,8 @@ namespace GlobalInputs {
return false;
}
auto wantsQuit() -> bool {
return quit_requested;
}
} // namespace GlobalInputs
+6 -1
View File
@@ -3,9 +3,14 @@
namespace GlobalInputs {
// Gestiona els atalls globals disponibles en qualsevol escena: zoom de
// finestra (F1/F2), fullscreen (F3), toggle shader (F4), tipus de shader
// POSTFX↔CRTPI (F5) i següent preset (F6). Cada hotkey emet una
// POSTFX↔CRTPI (F5), següent preset (F6) i la confirmació d'eixida amb
// ESC (Action::EXIT) en dues pulsacions. Cada hotkey emet una
// notificació localitzada. Retorna true si ha consumit alguna tecla (per
// si la capa cridant vol suprimir-la del processament específic de
// l'escena).
auto handle() -> bool;
// True si la doble pulsació d'ESC s'ha confirmat. Director consulta açò
// a iterate() per a posar `section_->name = SECTION_PROG_QUIT`.
[[nodiscard]] auto wantsQuit() -> bool;
} // namespace GlobalInputs
+20 -9
View File
@@ -16,13 +16,14 @@
#include <memory>
#include <string> // for basic_string, operator+, char_t...
#include "core/audio/audio.hpp" // for Audio::init, Audio::destroy
#include "core/input/input.h" // for Input, InputAction
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
#include "core/locale/lang.h" // for Lang, Lang::Code
#include "core/rendering/screen.h" // for Screen
#include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset, Asset::Type
#include "core/audio/audio.hpp" // for Audio::init, Audio::destroy
#include "core/input/global_inputs.hpp" // for GlobalInputs::wantsQuit
#include "core/input/input.h" // for Input, InputAction
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
#include "core/locale/lang.h" // for Lang, Lang::Code
#include "core/rendering/screen.h" // for Screen
#include "core/rendering/texture.h" // for Texture
#include "core/resources/asset.h" // for Asset, Asset::Type
#include "core/resources/resource.h"
#include "core/resources/resource_helper.h"
#include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H...
@@ -218,9 +219,12 @@ void Director::initInput() {
// Teclado - Otros
Input::get()->bindKey(Input::Action::ACCEPT, SDL_SCANCODE_RETURN);
Input::get()->bindKey(Input::Action::CANCEL, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(Input::Action::PAUSE, SDL_SCANCODE_ESCAPE);
// ESC només dispara EXIT (gestionat globalment per GlobalInputs com a
// confirmació de doble pulsació). PAUSE i CANCEL tenen tecles dedicades
// perquè cap escena ha de tractar ESC localment.
Input::get()->bindKey(Input::Action::EXIT, SDL_SCANCODE_ESCAPE);
Input::get()->bindKey(Input::Action::CANCEL, SDL_SCANCODE_BACKSPACE);
Input::get()->bindKey(Input::Action::PAUSE, SDL_SCANCODE_F12);
Input::get()->bindKey(Input::Action::WINDOW_DEC_ZOOM, SDL_SCANCODE_F1);
Input::get()->bindKey(Input::Action::WINDOW_INC_ZOOM, SDL_SCANCODE_F2);
Input::get()->bindKey(Input::Action::WINDOW_FULLSCREEN, SDL_SCANCODE_F3);
@@ -579,6 +583,13 @@ void Director::handleSectionTransition() {
// Ejecuta un frame del juego
auto Director::iterate() -> SDL_AppResult {
#ifndef __EMSCRIPTEN__
// Doble pulsació d'ESC confirmada des de qualsevol escena.
if (GlobalInputs::wantsQuit()) {
section_->name = SECTION_PROG_QUIT;
}
#endif
#ifdef __EMSCRIPTEN__
// En WASM no se puede salir: reinicia al logo
if (section->name == SECTION_PROG_QUIT) {
+10 -9
View File
@@ -2809,6 +2809,16 @@ void Game::updatePauseMenuUI() {
pause_menu_->update();
pause_menu_->checkInput();
// F12 (Action::PAUSE) també tanca el menú de pausa — mateix comportament
// que seleccionar "Continue" / cancel·lar amb BACKSPACE.
if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF)) {
leaving_pause_menu_ = true;
if (!Options::gameplay.pause_countdown) {
pause_counter_ = 0;
}
return;
}
switch (pause_menu_->getItemSelected()) {
case 1:
leaving_pause_menu_ = true;
@@ -2884,15 +2894,6 @@ void Game::enterPausedGame() {
Audio::get()->pauseMusic();
}
// ESC esta vinculada a PAUSE, CANCEL y EXIT a la vez (director.cpp), y cada
// Action tiene su propio flag de edge-trigger. La pulsacion que nos ha
// traido aqui solo ha actualizado el flag de PAUSE; CANCEL y EXIT siguen
// a false y dispararian un falso flanco la primera vez que el menu de
// pausa los lea. Hacemos una lectura sincronizadora descartada para
// ponerlos al dia con el estado real de la tecla.
Input::get()->checkInput(Input::Action::CANCEL, Input::Repeat::OFF);
Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF);
// Reinicia el menu
pause_menu_->reset();
leaving_pause_menu_ = false;
+2 -7
View File
@@ -210,13 +210,8 @@ void Instructions::checkEvents() {
// Comprueba las entradas
void Instructions::checkInput() {
#ifndef __EMSCRIPTEN__
if (Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF)) {
quit_requested_ = true;
finished_ = true;
return;
}
#endif
// ESC (Action::EXIT) ja el gestiona GlobalInputs::handle() amb doble
// pulsació; el quit es propaga via Director::iterate.
if (GlobalInputs::handle()) { return; }
if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::ACCEPT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_LEFT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_CENTER, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_RIGHT, Input::Repeat::OFF)) {
+2 -6
View File
@@ -166,12 +166,8 @@ Intro::~Intro() {
// Comprueba las entradas
void Intro::checkInput() {
#ifndef __EMSCRIPTEN__
if (Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF)) {
section_->name = SECTION_PROG_QUIT;
return;
}
#endif
// ESC (Action::EXIT) ja el gestiona GlobalInputs::handle() amb doble
// pulsació; el quit es propaga via Director::iterate.
if (GlobalInputs::handle()) { return; }
if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::ACCEPT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_LEFT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_CENTER, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_RIGHT, Input::Repeat::OFF)) {
+2 -6
View File
@@ -56,12 +56,8 @@ void Logo::checkLogoEnd() {
// Comprueba las entradas
void Logo::checkInput() {
#ifndef __EMSCRIPTEN__
if (Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF)) {
section_->name = SECTION_PROG_QUIT;
return;
}
#endif
// ESC (Action::EXIT) ja el gestiona GlobalInputs::handle() amb doble
// pulsació; el quit es propaga via Director::iterate.
if (GlobalInputs::handle()) { return; }
if (Input::get()->checkInput(Input::Action::PAUSE, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::ACCEPT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_LEFT, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_CENTER, Input::Repeat::OFF) || Input::get()->checkInput(Input::Action::FIRE_RIGHT, Input::Repeat::OFF)) {
+3 -7
View File
@@ -619,14 +619,10 @@ void Title::render() {
}
}
// Comprueba las entradas
// Comprueba las entradas. ESC (Action::EXIT) ja el gestiona
// GlobalInputs::handle() amb doble pulsació; el quit es propaga via
// Director::iterate.
void Title::checkInput() {
#ifndef __EMSCRIPTEN__
if (Input::get()->checkInput(Input::Action::EXIT, Input::Repeat::OFF)) {
section_->name = SECTION_PROG_QUIT;
return;
}
#endif
GlobalInputs::handle();
}
+15 -15
View File
@@ -74,17 +74,17 @@ class Title {
// Variables
Ja::Music *title_music_; // Musica para el titulo
Ja::Sound *crash_sound_; // Sonido con el impacto del título
int background_counter_; // Temporizador para el fondo de tiles de la pantalla de titulo
int counter_; // Temporizador para la pantalla de titulo
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
float sin_[360]; // Vector con los valores del seno precalculados
bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
bool demo_; // Indica si el modo demo estará activo
Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
Uint8 post_fade_; // Opción a realizar cuando termina el fundido
MenuData menu_; // Variable con todos los objetos menus y sus variables
int background_counter_; // Temporizador para el fondo de tiles de la pantalla de titulo
int counter_; // Temporizador para la pantalla de titulo
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo
float sin_[360]; // Vector con los valores del seno precalculados
bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente
bool demo_; // Indica si el modo demo estará activo
Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo
Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa
Uint8 post_fade_; // Opción a realizar cuando termina el fundido
MenuData menu_; // Variable con todos los objetos menus y sus variables
// Snapshot per a permetre CANCEL al menú d'opcions.
Options::Video prev_video_;
Options::Window prev_window_;
@@ -105,10 +105,10 @@ class Title {
Instructions::Mode instructions_mode_{Instructions::Mode::AUTO}; // Modo de las instrucciones activas
bool demo_then_instructions_; // Indica si tras la demo hay que mostrar instrucciones
void init(); // Inicializa los valores
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
void checkInput(); // Comprueba las entradas
void init(); // Inicializa los valores
void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
static void checkInput(); // Comprueba las entradas (només delega a GlobalInputs)
// Helpers de update, uno por cada subsección y por cada switch dentro del título 3
void updateTitle1();