Merge branch 'ui-fixes'
This commit is contained in:
+13
-1
@@ -1,7 +1,19 @@
|
|||||||
# CMakeLists.txt
|
# CMakeLists.txt
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(coffee_crisis VERSION 1.00)
|
|
||||||
|
# La versió de l'app es defineix una sola vegada a source/utils/defines.hpp
|
||||||
|
# (Defines::VERSION). El Makefile ja la grepeja per als noms de release; aqui
|
||||||
|
# l'extreiem perque project(... VERSION ...) i tots els consumidors interns
|
||||||
|
# de CMake (CPack, install, etc.) usin la mateixa font de veritat.
|
||||||
|
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/source/utils/defines.hpp" _DEFINES_CONTENT)
|
||||||
|
string(REGEX MATCH "VERSION = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" _ "${_DEFINES_CONTENT}")
|
||||||
|
set(APP_VERSION "${CMAKE_MATCH_1}")
|
||||||
|
if(APP_VERSION STREQUAL "")
|
||||||
|
message(FATAL_ERROR "No s'ha pogut extreure VERSION de source/utils/defines.hpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(coffee_crisis VERSION ${APP_VERSION})
|
||||||
|
|
||||||
# Tipus de build per defecte (Debug) si no se n'ha especificat cap
|
# Tipus de build per defecte (Debug) si no se n'ha especificat cap
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include "core/rendering/notifications.hpp"
|
#include "core/rendering/notifications.hpp"
|
||||||
#include "core/rendering/screen.h"
|
#include "core/rendering/screen.h"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
|
#include "utils/defines.hpp"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
namespace GlobalInputs {
|
namespace GlobalInputs {
|
||||||
|
|
||||||
@@ -54,6 +56,23 @@ namespace GlobalInputs {
|
|||||||
Notifications::show(MSG, Notifications::Palette::SUCCESS, Notifications::STANDARD_MS);
|
Notifications::show(MSG, Notifications::Palette::SUCCESS, Notifications::STANDARD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void notifyVersion() {
|
||||||
|
// Format: "<APP_NAME> v<VERSION> (<GIT_HASH>)"
|
||||||
|
const std::string MSG = std::string(Version::APP_NAME) + " v" + Texts::VERSION + " (" + Version::GIT_HASH + ")";
|
||||||
|
Notifications::show(MSG, Notifications::Palette::TOGGLE, Notifications::LONG_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyVSync() {
|
||||||
|
const std::string STATE = Options::video.vsync ? "ON" : "OFF";
|
||||||
|
const std::string MSG = std::string("VSync ") + STATE;
|
||||||
|
Notifications::show(MSG, Notifications::Palette::TOGGLE, Notifications::STANDARD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyPresentationMode() {
|
||||||
|
const std::string MSG = std::string("Mode ") + Screen::getPresentationModeName();
|
||||||
|
Notifications::show(MSG, Notifications::Palette::CHOICE, Notifications::STANDARD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
void onExit() {
|
void onExit() {
|
||||||
const Uint32 NOW = SDL_GetTicks();
|
const Uint32 NOW = SDL_GetTicks();
|
||||||
if (NOW < exit_window_until_ticks) {
|
if (NOW < exit_window_until_ticks) {
|
||||||
@@ -94,6 +113,24 @@ namespace GlobalInputs {
|
|||||||
notifyShaderEnabled();
|
notifyShaderEnabled();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (Input::get()->checkInput(Input::Action::SHOW_VERSION, Input::Repeat::OFF)) {
|
||||||
|
notifyVersion();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(Input::Action::TOGGLE_VSYNC, Input::Repeat::OFF)) {
|
||||||
|
Screen::get()->toggleVSync();
|
||||||
|
notifyVSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(Input::Action::NEXT_PRESENTATION_MODE, Input::Repeat::OFF)) {
|
||||||
|
Screen::get()->nextPresentationMode();
|
||||||
|
notifyPresentationMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Input::get()->checkInput(Input::Action::TOGGLE_FPS, Input::Repeat::OFF)) {
|
||||||
|
Screen::get()->toggleFps();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// F5/F6 només actuen quan el post-procesado està actiu.
|
// F5/F6 només actuen quan el post-procesado està actiu.
|
||||||
if (Screen::isShaderEnabled()) {
|
if (Screen::isShaderEnabled()) {
|
||||||
if (Input::get()->checkInput(Input::Action::TOGGLE_SHADER_TYPE, Input::Repeat::OFF)) {
|
if (Input::get()->checkInput(Input::Action::TOGGLE_SHADER_TYPE, Input::Repeat::OFF)) {
|
||||||
|
|||||||
@@ -157,25 +157,45 @@ auto Input::checkGameControllerInput(Action input, Repeat repeat, int index) ->
|
|||||||
return PRESS_EDGE;
|
return PRESS_EDGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba si hay almenos un input activo
|
// Comprueba si hay almenos un input "humano" activo (moviment, ACCEPT/CANCEL,
|
||||||
|
// FIRE_*). Exclou les accions reservades a hotkeys globals (EXIT, PAUSE,
|
||||||
|
// WINDOW_*, *SHADER*) perque prémer F1-F12 o ESC no s'ha de comptar com
|
||||||
|
// "qualsevol tecla" — ningu vol saltar una intro per modificar el zoom.
|
||||||
auto Input::checkAnyInput(Device device, int index) -> bool {
|
auto Input::checkAnyInput(Device device, int index) -> bool {
|
||||||
if (device == Device::ANY) {
|
if (device == Device::ANY) {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto is_skippable = [](Action a) {
|
||||||
|
switch (a) {
|
||||||
|
case Action::UP:
|
||||||
|
case Action::DOWN:
|
||||||
|
case Action::LEFT:
|
||||||
|
case Action::RIGHT:
|
||||||
|
case Action::ACCEPT:
|
||||||
|
case Action::CANCEL:
|
||||||
|
case Action::FIRE_LEFT:
|
||||||
|
case Action::FIRE_CENTER:
|
||||||
|
case Action::FIRE_RIGHT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (device == Device::KEYBOARD || device == Device::ANY) {
|
if (device == Device::KEYBOARD || device == Device::ANY) {
|
||||||
const bool *key_states = SDL_GetKeyboardState(nullptr);
|
const bool *key_states = SDL_GetKeyboardState(nullptr);
|
||||||
|
for (std::size_t i = 0; i < key_bindings_.size(); ++i) {
|
||||||
if (std::ranges::any_of(key_bindings_,
|
if (is_skippable(static_cast<Action>(i)) && key_states[key_bindings_[i].scancode]) {
|
||||||
[key_states](const auto &key_binding) { return key_states[key_binding.scancode]; })) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gameControllerFound() && index >= 0 && index < (int)connected_controllers_.size()) {
|
if (gameControllerFound() && index >= 0 && index < (int)connected_controllers_.size()) {
|
||||||
if (device == Device::GAMECONTROLLER || device == Device::ANY) {
|
if (device == Device::GAMECONTROLLER || device == Device::ANY) {
|
||||||
for (auto &game_controller_binding : game_controller_bindings_) {
|
for (std::size_t i = 0; i < game_controller_bindings_.size(); ++i) {
|
||||||
if (SDL_GetGamepadButton(connected_controllers_[index], game_controller_binding.button)) {
|
if (is_skippable(static_cast<Action>(i)) && SDL_GetGamepadButton(connected_controllers_[index], game_controller_bindings_[i].button)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ class Input {
|
|||||||
TOGGLE_SHADER,
|
TOGGLE_SHADER,
|
||||||
TOGGLE_SHADER_TYPE,
|
TOGGLE_SHADER_TYPE,
|
||||||
|
|
||||||
|
// Diagnostic / video toggles
|
||||||
|
SHOW_VERSION,
|
||||||
|
TOGGLE_VSYNC,
|
||||||
|
NEXT_PRESENTATION_MODE,
|
||||||
|
TOGGLE_FPS,
|
||||||
|
|
||||||
// Centinela final (usar para sizing)
|
// Centinela final (usar para sizing)
|
||||||
NUMBER_OF_INPUTS
|
NUMBER_OF_INPUTS
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
namespace Notifications {
|
namespace Notifications {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Paleta pastel. Per a tunejar l'aparença només cal tocar aquí.
|
// Paleta semi-saturada: a mig cami entre pastel i color "pur". Manté
|
||||||
|
// contrast del outline (foscor) sense diluir el matís.
|
||||||
// (Color no és literal type ⇒ const, no constexpr.)
|
// (Color no és literal type ⇒ const, no constexpr.)
|
||||||
const Color INFO_COLOR{0xF0, 0xE0, 0x90}; // groc trigo
|
const Color INFO_COLOR{0xF0, 0xD0, 0x40}; // groc
|
||||||
const Color TOGGLE_COLOR{0xA0, 0xE0, 0xF0}; // cian gel
|
const Color TOGGLE_COLOR{0x60, 0xC0, 0xF0}; // cian
|
||||||
const Color CHOICE_COLOR{0xE0, 0xA0, 0xE0}; // rosa orquídia
|
const Color CHOICE_COLOR{0xD0, 0x60, 0xD0}; // magenta
|
||||||
const Color SUCCESS_COLOR{0xB0, 0xE6, 0xB0}; // verd menta
|
const Color SUCCESS_COLOR{0x70, 0xD0, 0x70}; // verd
|
||||||
const Color DANGER_COLOR{0xF0, 0xA0, 0xA0}; // rosa salmó
|
const Color DANGER_COLOR{0xF0, 0x60, 0x60}; // vermell
|
||||||
|
|
||||||
// Factor de foscor per a l'outline (~40% de la lluminositat del
|
// Factor de foscor per a l'outline (~40% de la lluminositat del
|
||||||
// color base): manté el matís i queda prou fosc per a contrastar
|
// color base): manté el matís i queda prou fosc per a contrastar
|
||||||
|
|||||||
@@ -169,9 +169,12 @@ void Screen::start() {
|
|||||||
|
|
||||||
// Vuelca el contenido del renderizador en pantalla
|
// Vuelca el contenido del renderizador en pantalla
|
||||||
void Screen::blit() {
|
void Screen::blit() {
|
||||||
// Dibuja la notificación activa sobre el gameCanvas antes de presentar
|
updateFps();
|
||||||
|
|
||||||
|
// Dibuja la notificación activa i, si toca, l'overlay de FPS sobre el gameCanvas
|
||||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||||
renderNotification();
|
renderNotification();
|
||||||
|
renderFps();
|
||||||
|
|
||||||
#ifndef NO_SHADERS
|
#ifndef NO_SHADERS
|
||||||
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
|
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
|
||||||
@@ -326,16 +329,27 @@ void Screen::detectMaxZoom() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el escalado entero
|
// Estableix el mode de presentacio del canvas i reaplica el layout
|
||||||
void Screen::setIntegerScale(bool enabled) {
|
void Screen::setPresentationMode(Options::PresentationMode mode) {
|
||||||
if (Options::video.integer_scale == enabled) { return; }
|
if (Options::video.presentation_mode == mode) { return; }
|
||||||
Options::video.integer_scale = enabled;
|
Options::video.presentation_mode = mode;
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setPresentationMode(static_cast<Rendering::ShaderBackend::PresentationMode>(mode));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
setVideoMode(Options::video.fullscreen);
|
setVideoMode(Options::video.fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alterna el escalado entero
|
// Cicla integer_scale -> letterbox -> stretched -> overscan -> integer_scale
|
||||||
void Screen::toggleIntegerScale() {
|
void Screen::nextPresentationMode() {
|
||||||
setIntegerScale(!Options::video.integer_scale);
|
setPresentationMode(Options::nextPresentationMode(Options::video.presentation_mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nom curt del mode actual (per a notificacions). Static perque no necessita
|
||||||
|
// estat d'instancia: nomes consulta Options::video.
|
||||||
|
auto Screen::getPresentationModeName() -> const char * {
|
||||||
|
return Options::presentationModeToString(Options::video.presentation_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el V-Sync
|
// Establece el V-Sync
|
||||||
@@ -407,39 +421,75 @@ void Screen::applyFullscreenLayout() {
|
|||||||
computeFullscreenGameRect();
|
computeFullscreenGameRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcula el rectángulo dest para fullscreen: integer_scale / aspect ratio
|
// Calcula el rectangle dest segons el PresentationMode actiu.
|
||||||
|
// INTEGER_SCALE: x sencera maxima (1x, 2x, 3x...) centrada amb barres.
|
||||||
|
// LETTERBOX: mante aspect ratio, ajusta al menor dels eixos, barres.
|
||||||
|
// STRETCHED: omple tota la finestra deformant la relacio d'aspecte.
|
||||||
|
// OVERSCAN: mante aspect ratio omplint la finestra (retalla el sobrant).
|
||||||
void Screen::computeFullscreenGameRect() {
|
void Screen::computeFullscreenGameRect() {
|
||||||
if (Options::video.integer_scale) {
|
const float CANVAS_RATIO = static_cast<float>(game_canvas_width_) / static_cast<float>(game_canvas_height_);
|
||||||
// Calcula el tamaño de la escala máxima
|
const float WINDOW_RATIO = static_cast<float>(window_width_) / static_cast<float>(window_height_);
|
||||||
|
|
||||||
|
switch (Options::video.presentation_mode) {
|
||||||
|
case Options::PresentationMode::INTEGER_SCALE: {
|
||||||
int scale = 0;
|
int scale = 0;
|
||||||
while (((game_canvas_width_ * (scale + 1)) <= window_width_) && ((game_canvas_height_ * (scale + 1)) <= window_height_)) {
|
while (((game_canvas_width_ * (scale + 1)) <= window_width_) && ((game_canvas_height_ * (scale + 1)) <= window_height_)) {
|
||||||
scale++;
|
scale++;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest_.w = game_canvas_width_ * scale;
|
dest_.w = game_canvas_width_ * scale;
|
||||||
dest_.h = game_canvas_height_ * scale;
|
dest_.h = game_canvas_height_ * scale;
|
||||||
dest_.x = (window_width_ - dest_.w) / 2;
|
break;
|
||||||
dest_.y = (window_height_ - dest_.h) / 2;
|
}
|
||||||
} else {
|
case Options::PresentationMode::LETTERBOX: {
|
||||||
// Manté la relació d'aspecte sense escalat enter (letterbox/pillarbox).
|
if (WINDOW_RATIO >= CANVAS_RATIO) {
|
||||||
float ratio = (float)game_canvas_width_ / (float)game_canvas_height_;
|
|
||||||
if ((window_width_ - game_canvas_width_) >= (window_height_ - game_canvas_height_)) {
|
|
||||||
dest_.h = window_height_;
|
dest_.h = window_height_;
|
||||||
dest_.w = static_cast<int>(std::lround(window_height_ * ratio));
|
dest_.w = static_cast<int>(std::lround(window_height_ * CANVAS_RATIO));
|
||||||
dest_.x = (window_width_ - dest_.w) / 2;
|
|
||||||
dest_.y = (window_height_ - dest_.h) / 2;
|
|
||||||
} else {
|
} else {
|
||||||
dest_.w = window_width_;
|
dest_.w = window_width_;
|
||||||
dest_.h = static_cast<int>(std::lround(window_width_ / ratio));
|
dest_.h = static_cast<int>(std::lround(window_width_ / CANVAS_RATIO));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Options::PresentationMode::STRETCHED: {
|
||||||
|
dest_.w = window_width_;
|
||||||
|
dest_.h = window_height_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Options::PresentationMode::OVERSCAN: {
|
||||||
|
// Mante aspect: dimensiona al major dels eixos (l'altre desborda).
|
||||||
|
if (WINDOW_RATIO >= CANVAS_RATIO) {
|
||||||
|
dest_.w = window_width_;
|
||||||
|
dest_.h = static_cast<int>(std::lround(window_width_ / CANVAS_RATIO));
|
||||||
|
} else {
|
||||||
|
dest_.h = window_height_;
|
||||||
|
dest_.w = static_cast<int>(std::lround(window_height_ * CANVAS_RATIO));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
dest_.x = (window_width_ - dest_.w) / 2;
|
dest_.x = (window_width_ - dest_.w) / 2;
|
||||||
dest_.y = (window_height_ - dest_.h) / 2;
|
dest_.y = (window_height_ - dest_.h) / 2;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aplica la logical presentation y persiste el estado en options
|
// Aplica la logical presentation segons el PresentationMode actiu (ruta SDL_Renderer fallback).
|
||||||
|
// La ruta GPU calcula el viewport ella mateixa via computeViewport().
|
||||||
void Screen::applyLogicalPresentation(bool fullscreen) {
|
void Screen::applyLogicalPresentation(bool fullscreen) {
|
||||||
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
SDL_RendererLogicalPresentation lp = SDL_LOGICAL_PRESENTATION_LETTERBOX;
|
||||||
|
switch (Options::video.presentation_mode) {
|
||||||
|
case Options::PresentationMode::INTEGER_SCALE:
|
||||||
|
lp = SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
|
||||||
|
break;
|
||||||
|
case Options::PresentationMode::LETTERBOX:
|
||||||
|
lp = SDL_LOGICAL_PRESENTATION_LETTERBOX;
|
||||||
|
break;
|
||||||
|
case Options::PresentationMode::STRETCHED:
|
||||||
|
lp = SDL_LOGICAL_PRESENTATION_STRETCH;
|
||||||
|
break;
|
||||||
|
case Options::PresentationMode::OVERSCAN:
|
||||||
|
lp = SDL_LOGICAL_PRESENTATION_OVERSCAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, lp);
|
||||||
Options::video.fullscreen = fullscreen;
|
Options::video.fullscreen = fullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,14 +511,16 @@ void Screen::clearNotification() {
|
|||||||
notification_message_.clear();
|
notification_message_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
// Dibuja la notificación activa (si la hay) sobre el gameCanvas. La Y es
|
||||||
|
// el `notification_y_` configurat, desplacat cap avall si en overscan part
|
||||||
|
// de la franja superior queda fora de pantalla.
|
||||||
void Screen::renderNotification() {
|
void Screen::renderNotification() {
|
||||||
if (notification_text_ == nullptr || SDL_GetTicks() >= notification_end_time_) {
|
if (notification_text_ == nullptr || SDL_GetTicks() >= notification_end_time_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notification_text_->writeDX(Text::FLAG_CENTER | Text::FLAG_COLOR | Text::FLAG_STROKE,
|
notification_text_->writeDX(Text::FLAG_CENTER | Text::FLAG_COLOR | Text::FLAG_STROKE,
|
||||||
game_canvas_width_ / 2,
|
game_canvas_width_ / 2,
|
||||||
notification_y_,
|
notification_y_ + safeNotificationY(),
|
||||||
notification_message_,
|
notification_message_,
|
||||||
1,
|
1,
|
||||||
notification_text_color_,
|
notification_text_color_,
|
||||||
@@ -476,6 +528,70 @@ void Screen::renderNotification() {
|
|||||||
notification_outline_color_);
|
notification_outline_color_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alterna la visibilitat de l'overlay de FPS. No persisteix a config.
|
||||||
|
void Screen::toggleFps() {
|
||||||
|
fps_visible_ = !fps_visible_;
|
||||||
|
if (fps_visible_) {
|
||||||
|
fps_window_start_ticks_ = SDL_GetTicks();
|
||||||
|
fps_frame_count_ = 0;
|
||||||
|
fps_value_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isFpsVisible() const -> bool {
|
||||||
|
return fps_visible_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acumula frames i recalcula el FPS cada segon real (no afectat per dt del joc).
|
||||||
|
// Cridat des de blit() una vegada per frame.
|
||||||
|
void Screen::updateFps() {
|
||||||
|
if (!fps_visible_) { return; }
|
||||||
|
++fps_frame_count_;
|
||||||
|
const Uint32 NOW = SDL_GetTicks();
|
||||||
|
const Uint32 ELAPSED = NOW - fps_window_start_ticks_;
|
||||||
|
if (ELAPSED >= 1000) {
|
||||||
|
fps_value_ = static_cast<int>((static_cast<Uint64>(fps_frame_count_) * 1000) / ELAPSED);
|
||||||
|
fps_frame_count_ = 0;
|
||||||
|
fps_window_start_ticks_ = NOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuixa "NN FPS" a dalt a la dreta del canvas, mateixa Y que les notificacions
|
||||||
|
// (incloent l'ajust per overscan) i amb la mateixa font 8bithud.
|
||||||
|
void Screen::renderFps() {
|
||||||
|
if (!fps_visible_ || notification_text_ == nullptr) { return; }
|
||||||
|
constexpr int RIGHT_MARGIN = 2;
|
||||||
|
const Color FPS_COLOR{0x60, 0xD0, 0x70}; // verd (mateix to que SUCCESS de notificacions)
|
||||||
|
const Color FPS_OUTLINE{0x26, 0x53, 0x2C}; // ~40% darken del verd
|
||||||
|
const std::string MSG = std::to_string(fps_value_) + " FPS";
|
||||||
|
const int TEXT_W = notification_text_->lenght(MSG, 1);
|
||||||
|
const int X = game_canvas_width_ - TEXT_W - RIGHT_MARGIN;
|
||||||
|
const int Y = notification_y_ + safeNotificationY();
|
||||||
|
notification_text_->writeDX(Text::FLAG_COLOR | Text::FLAG_STROKE,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
MSG,
|
||||||
|
1,
|
||||||
|
FPS_COLOR,
|
||||||
|
1,
|
||||||
|
FPS_OUTLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y minima del canvas visible. Solo no zero quan estem en overscan i l'aspect
|
||||||
|
// de finestra obliga a escalar mes ample que alt (el canvas vertical desborda
|
||||||
|
// i la franja superior es retalla). En cas contrari (qualsevol altre mode, o
|
||||||
|
// overscan amb retall horitzontal nomes), retorna 0.
|
||||||
|
auto Screen::safeNotificationY() const -> int {
|
||||||
|
if (Options::video.presentation_mode != Options::PresentationMode::OVERSCAN) { return 0; }
|
||||||
|
if (window_width_ <= 0 || window_height_ <= 0 || game_canvas_height_ <= 0) { return 0; }
|
||||||
|
const float CANVAS_RATIO = static_cast<float>(game_canvas_width_) / static_cast<float>(game_canvas_height_);
|
||||||
|
const float WINDOW_RATIO = static_cast<float>(window_width_) / static_cast<float>(window_height_);
|
||||||
|
if (WINDOW_RATIO < CANVAS_RATIO) { return 0; } // retall horitzontal nomes
|
||||||
|
const float OVERSCAN_SCALE = static_cast<float>(window_width_) / static_cast<float>(game_canvas_width_);
|
||||||
|
const float VH = static_cast<float>(game_canvas_height_) * OVERSCAN_SCALE;
|
||||||
|
return static_cast<int>(std::ceil((VH - static_cast<float>(window_height_)) / (2.0F * OVERSCAN_SCALE)));
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
|
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
|
||||||
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
|
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
|
||||||
@@ -545,7 +661,7 @@ void Screen::initShaders() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shader_backend_->isHardwareAccelerated()) {
|
if (shader_backend_->isHardwareAccelerated()) {
|
||||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
shader_backend_->setPresentationMode(static_cast<Rendering::ShaderBackend::PresentationMode>(Options::video.presentation_mode));
|
||||||
shader_backend_->setVSync(Options::video.vsync);
|
shader_backend_->setVSync(Options::video.vsync);
|
||||||
|
|
||||||
// Resol els índexs de preset a partir del nom emmagatzemat al config.
|
// Resol els índexs de preset a partir del nom emmagatzemat al config.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
|
#include "game/options.hpp" // for Options::PresentationMode
|
||||||
#include "utils/utils.h" // for Color
|
#include "utils/utils.h" // for Color
|
||||||
|
|
||||||
#ifndef NO_SHADERS
|
#ifndef NO_SHADERS
|
||||||
@@ -55,8 +56,9 @@ class Screen {
|
|||||||
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
|
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
|
||||||
void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
|
void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
|
||||||
static void syncFullscreenFlagFromBrowser(bool is_fullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
|
static void syncFullscreenFlagFromBrowser(bool is_fullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
|
||||||
void toggleIntegerScale(); // Alterna el escalado entero
|
void nextPresentationMode(); // Cicla integer_scale -> letterbox -> stretched -> overscan
|
||||||
void setIntegerScale(bool enabled); // Establece el escalado entero
|
void setPresentationMode(Options::PresentationMode mode); // Estableix el mode de presentacio del canvas
|
||||||
|
[[nodiscard]] static auto getPresentationModeName() -> const char *; // Nom curt del mode actual (per a notificacions)
|
||||||
void toggleVSync(); // Alterna el V-Sync
|
void toggleVSync(); // Alterna el V-Sync
|
||||||
void setVSync(bool enabled); // Establece el V-Sync
|
void setVSync(bool enabled); // Establece el V-Sync
|
||||||
auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió)
|
auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió)
|
||||||
@@ -71,6 +73,10 @@ class Screen {
|
|||||||
void notify(const std::string &text, Color text_color, Color outline_color, Uint32 duration_ms); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
void notify(const std::string &text, Color text_color, Color outline_color, Uint32 duration_ms); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
||||||
void clearNotification(); // Limpia la notificación actual
|
void clearNotification(); // Limpia la notificación actual
|
||||||
|
|
||||||
|
// FPS overlay (debug, no persistent)
|
||||||
|
void toggleFps(); // Alterna la visibilitat de l'overlay de FPS
|
||||||
|
[[nodiscard]] auto isFpsVisible() const -> bool; // Estat actual
|
||||||
|
|
||||||
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
|
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
|
||||||
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
|
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
|
||||||
void shutdownShaders(); // Libera el backend GPU
|
void shutdownShaders(); // Libera el backend GPU
|
||||||
@@ -111,6 +117,11 @@ class Screen {
|
|||||||
|
|
||||||
// Notificaciones
|
// Notificaciones
|
||||||
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||||
|
[[nodiscard]] auto safeNotificationY() const -> int; // Y minima dins del canvas que segueix sent visible en overscan (segons aspect ratio finestra/canvas)
|
||||||
|
|
||||||
|
// FPS overlay
|
||||||
|
void updateFps(); // Acumula temps i recalcula fps cada segon (a cridar des de blit)
|
||||||
|
void renderFps(); // Dibuixa "NN FPS" a dalt a la dreta del canvas
|
||||||
|
|
||||||
#ifndef NO_SHADERS
|
#ifndef NO_SHADERS
|
||||||
// Aplica els paràmetres actuals del shader al backend segons options
|
// Aplica els paràmetres actuals del shader al backend segons options
|
||||||
@@ -139,6 +150,12 @@ class Screen {
|
|||||||
Uint32 notification_end_time_; // SDL_GetTicks() hasta el cual se muestra
|
Uint32 notification_end_time_; // SDL_GetTicks() hasta el cual se muestra
|
||||||
int notification_y_; // Fila vertical en el canvas virtual
|
int notification_y_; // Fila vertical en el canvas virtual
|
||||||
|
|
||||||
|
// FPS overlay (debug, no persistent)
|
||||||
|
bool fps_visible_{false}; // F10 toggle
|
||||||
|
int fps_value_{0}; // Ultim valor calculat (frames per segon)
|
||||||
|
int fps_frame_count_{0}; // Frames acumulats durant la finestra actual
|
||||||
|
Uint32 fps_window_start_ticks_{0}; // Inici de la finestra d'1s actual (SDL_GetTicks)
|
||||||
|
|
||||||
#ifndef NO_SHADERS
|
#ifndef NO_SHADERS
|
||||||
// GPU / shaders
|
// GPU / shaders
|
||||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
|
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
|
||||||
|
|||||||
@@ -292,21 +292,48 @@ namespace Rendering {
|
|||||||
// computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox)
|
// computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport {
|
auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport {
|
||||||
|
const auto SWF = static_cast<float>(sw);
|
||||||
|
const auto SHF = static_cast<float>(sh);
|
||||||
|
const float CANVAS_RATIO = static_cast<float>(game_width_) / static_cast<float>(game_height_);
|
||||||
|
const float WINDOW_RATIO = SWF / SHF;
|
||||||
|
|
||||||
float vw = 0.0F;
|
float vw = 0.0F;
|
||||||
float vh = 0.0F;
|
float vh = 0.0F;
|
||||||
if (integer_scale_) {
|
switch (presentation_mode_) {
|
||||||
|
case PresentationMode::INTEGER_SCALE: {
|
||||||
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
|
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
|
||||||
vw = static_cast<float>(game_width_ * SCALE);
|
vw = static_cast<float>(game_width_ * SCALE);
|
||||||
vh = static_cast<float>(game_height_ * SCALE);
|
vh = static_cast<float>(game_height_ * SCALE);
|
||||||
} else {
|
break;
|
||||||
const float SCALE = std::min(
|
|
||||||
static_cast<float>(sw) / static_cast<float>(game_width_),
|
|
||||||
static_cast<float>(sh) / static_cast<float>(game_height_));
|
|
||||||
vw = static_cast<float>(game_width_) * SCALE;
|
|
||||||
vh = static_cast<float>(game_height_) * SCALE;
|
|
||||||
}
|
}
|
||||||
const float VX = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
case PresentationMode::LETTERBOX: {
|
||||||
const float VY = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
if (WINDOW_RATIO >= CANVAS_RATIO) {
|
||||||
|
vh = SHF;
|
||||||
|
vw = SHF * CANVAS_RATIO;
|
||||||
|
} else {
|
||||||
|
vw = SWF;
|
||||||
|
vh = SWF / CANVAS_RATIO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PresentationMode::STRETCHED: {
|
||||||
|
vw = SWF;
|
||||||
|
vh = SHF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PresentationMode::OVERSCAN: {
|
||||||
|
if (WINDOW_RATIO >= CANVAS_RATIO) {
|
||||||
|
vw = SWF;
|
||||||
|
vh = SWF / CANVAS_RATIO;
|
||||||
|
} else {
|
||||||
|
vh = SHF;
|
||||||
|
vw = SHF * CANVAS_RATIO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float VX = std::floor((SWF - vw) * 0.5F);
|
||||||
|
const float VY = std::floor((SHF - vh) * 0.5F);
|
||||||
return {.x = VX, .y = VY, .w = vw, .h = vh};
|
return {.x = VX, .y = VY, .w = vw, .h = vh};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,8 +596,8 @@ namespace Rendering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDL3GPUShader::setScaleMode(bool integer_scale) {
|
void SDL3GPUShader::setPresentationMode(PresentationMode mode) {
|
||||||
integer_scale_ = integer_scale;
|
presentation_mode_ = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ namespace Rendering {
|
|||||||
// Activa/desactiva VSync en el swapchain
|
// Activa/desactiva VSync en el swapchain
|
||||||
void setVSync(bool vsync) override;
|
void setVSync(bool vsync) override;
|
||||||
|
|
||||||
// Activa/desactiva escalado entero (integer scale)
|
// Estableix el mode de presentacio del canvas
|
||||||
void setScaleMode(bool integer_scale) override;
|
void setPresentationMode(PresentationMode mode) override;
|
||||||
|
|
||||||
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||||
void setActiveShader(ShaderType type) override;
|
void setActiveShader(ShaderType type) override;
|
||||||
@@ -195,7 +195,7 @@ namespace Rendering {
|
|||||||
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||||
bool is_initialized_ = false;
|
bool is_initialized_ = false;
|
||||||
bool vsync_ = true;
|
bool vsync_ = true;
|
||||||
bool integer_scale_ = false;
|
PresentationMode presentation_mode_ = PresentationMode::INTEGER_SCALE;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -112,9 +112,16 @@ namespace Rendering {
|
|||||||
virtual void setVSync(bool /*vsync*/) {}
|
virtual void setVSync(bool /*vsync*/) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Activa o desactiva el escalado entero (integer scale)
|
* @brief Estableix el mode de presentacio del canvas dins del swapchain.
|
||||||
|
* El backend calcula el viewport en consequencia.
|
||||||
*/
|
*/
|
||||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
enum class PresentationMode : std::uint8_t {
|
||||||
|
INTEGER_SCALE,
|
||||||
|
LETTERBOX,
|
||||||
|
STRETCHED,
|
||||||
|
OVERSCAN
|
||||||
|
};
|
||||||
|
virtual void setPresentationMode(PresentationMode /*mode*/) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Verifica si el backend está usando aceleración por hardware
|
* @brief Verifica si el backend está usando aceleración por hardware
|
||||||
|
|||||||
@@ -232,6 +232,10 @@ void Director::initInput() {
|
|||||||
Input::get()->bindKey(Input::Action::TOGGLE_SHADER, SDL_SCANCODE_F4);
|
Input::get()->bindKey(Input::Action::TOGGLE_SHADER, SDL_SCANCODE_F4);
|
||||||
Input::get()->bindKey(Input::Action::TOGGLE_SHADER_TYPE, SDL_SCANCODE_F5);
|
Input::get()->bindKey(Input::Action::TOGGLE_SHADER_TYPE, SDL_SCANCODE_F5);
|
||||||
Input::get()->bindKey(Input::Action::NEXT_SHADER_PRESET, SDL_SCANCODE_F6);
|
Input::get()->bindKey(Input::Action::NEXT_SHADER_PRESET, SDL_SCANCODE_F6);
|
||||||
|
Input::get()->bindKey(Input::Action::TOGGLE_VSYNC, SDL_SCANCODE_F7);
|
||||||
|
Input::get()->bindKey(Input::Action::NEXT_PRESENTATION_MODE, SDL_SCANCODE_F8);
|
||||||
|
Input::get()->bindKey(Input::Action::TOGGLE_FPS, SDL_SCANCODE_F10);
|
||||||
|
Input::get()->bindKey(Input::Action::SHOW_VERSION, SDL_SCANCODE_F11);
|
||||||
|
|
||||||
// Mando - Movimiento del jugador
|
// Mando - Movimiento del jugador
|
||||||
Input::get()->bindGameControllerButton(Input::Action::UP, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
Input::get()->bindGameControllerButton(Input::Action::UP, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace Defaults::Video {
|
|||||||
constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
|
constexpr SDL_ScaleMode SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
|
||||||
constexpr bool FULLSCREEN = false;
|
constexpr bool FULLSCREEN = false;
|
||||||
constexpr bool VSYNC = true;
|
constexpr bool VSYNC = true;
|
||||||
constexpr bool INTEGER_SCALE = true;
|
// INTEGER_SCALE eliminat: ara es part de PresentationMode (a options.hpp).
|
||||||
|
// El default es defineix literal alli: PresentationMode::INTEGER_SCALE.
|
||||||
constexpr bool GPU_ACCELERATION = true;
|
constexpr bool GPU_ACCELERATION = true;
|
||||||
constexpr const char *GPU_PREFERRED_DRIVER = "";
|
constexpr const char *GPU_PREFERRED_DRIVER = "";
|
||||||
constexpr bool SHADER_ENABLED = false;
|
constexpr bool SHADER_ENABLED = false;
|
||||||
|
|||||||
+49
-3
@@ -31,6 +31,42 @@ namespace Options {
|
|||||||
std::string crtpi_file_path;
|
std::string crtpi_file_path;
|
||||||
int current_crtpi_preset = 0;
|
int current_crtpi_preset = 0;
|
||||||
|
|
||||||
|
// Conversions PresentationMode <-> string per a config.yaml i UI
|
||||||
|
auto presentationModeToString(PresentationMode m) -> const char * {
|
||||||
|
switch (m) {
|
||||||
|
case PresentationMode::INTEGER_SCALE:
|
||||||
|
return "integer_scale";
|
||||||
|
case PresentationMode::LETTERBOX:
|
||||||
|
return "letterbox";
|
||||||
|
case PresentationMode::STRETCHED:
|
||||||
|
return "stretched";
|
||||||
|
case PresentationMode::OVERSCAN:
|
||||||
|
return "overscan";
|
||||||
|
}
|
||||||
|
return "integer_scale";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto presentationModeFromString(const std::string &s) -> PresentationMode {
|
||||||
|
if (s == "letterbox") { return PresentationMode::LETTERBOX; }
|
||||||
|
if (s == "stretched") { return PresentationMode::STRETCHED; }
|
||||||
|
if (s == "overscan") { return PresentationMode::OVERSCAN; }
|
||||||
|
return PresentationMode::INTEGER_SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextPresentationMode(PresentationMode m) -> PresentationMode {
|
||||||
|
switch (m) {
|
||||||
|
case PresentationMode::INTEGER_SCALE:
|
||||||
|
return PresentationMode::LETTERBOX;
|
||||||
|
case PresentationMode::LETTERBOX:
|
||||||
|
return PresentationMode::STRETCHED;
|
||||||
|
case PresentationMode::STRETCHED:
|
||||||
|
return PresentationMode::OVERSCAN;
|
||||||
|
case PresentationMode::OVERSCAN:
|
||||||
|
return PresentationMode::INTEGER_SCALE;
|
||||||
|
}
|
||||||
|
return PresentationMode::INTEGER_SCALE;
|
||||||
|
}
|
||||||
|
|
||||||
// Lectura tolerant d'un camp YAML: assigna a `target` el valor del camp
|
// Lectura tolerant d'un camp YAML: assigna a `target` el valor del camp
|
||||||
// si existeix i el tipus encaixa. Si la clau no hi és o el tipus YAML
|
// si existeix i el tipus encaixa. Si la clau no hi és o el tipus YAML
|
||||||
// no és compatible amb T, conserva el valor previ de `target` (default).
|
// no és compatible amb T, conserva el valor previ de `target` (default).
|
||||||
@@ -82,7 +118,16 @@ namespace Options {
|
|||||||
|
|
||||||
parseBoolField(vid, "fullscreen", video.fullscreen);
|
parseBoolField(vid, "fullscreen", video.fullscreen);
|
||||||
parseBoolField(vid, "vsync", video.vsync);
|
parseBoolField(vid, "vsync", video.vsync);
|
||||||
parseBoolField(vid, "integer_scale", video.integer_scale);
|
// presentation_mode (nou): prefereix string explicit; cau a integer_scale legacy (bool) si no.
|
||||||
|
std::string pm_str;
|
||||||
|
if (tryGet<std::string>(vid, "presentation_mode", pm_str)) {
|
||||||
|
video.presentation_mode = presentationModeFromString(pm_str);
|
||||||
|
} else {
|
||||||
|
bool legacy_integer_scale = true;
|
||||||
|
if (tryGet<bool>(vid, "integer_scale", legacy_integer_scale)) {
|
||||||
|
video.presentation_mode = legacy_integer_scale ? PresentationMode::INTEGER_SCALE : PresentationMode::LETTERBOX;
|
||||||
|
}
|
||||||
|
}
|
||||||
int scale_mode_int = static_cast<int>(video.scale_mode);
|
int scale_mode_int = static_cast<int>(video.scale_mode);
|
||||||
if (tryGet<int>(vid, "scale_mode", scale_mode_int)) {
|
if (tryGet<int>(vid, "scale_mode", scale_mode_int)) {
|
||||||
video.scale_mode = static_cast<SDL_ScaleMode>(scale_mode_int);
|
video.scale_mode = static_cast<SDL_ScaleMode>(scale_mode_int);
|
||||||
@@ -197,7 +242,7 @@ namespace Options {
|
|||||||
// En Emscripten la ventana la gestiona el navegador
|
// En Emscripten la ventana la gestiona el navegador
|
||||||
window.zoom = 4;
|
window.zoom = 4;
|
||||||
video.fullscreen = false;
|
video.fullscreen = false;
|
||||||
video.integer_scale = true;
|
video.presentation_mode = PresentationMode::INTEGER_SCALE;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Dispositius d'entrada per defecte
|
// Dispositius d'entrada per defecte
|
||||||
@@ -283,7 +328,8 @@ namespace Options {
|
|||||||
file << "video:\n";
|
file << "video:\n";
|
||||||
file << " fullscreen: " << boolToString(video.fullscreen) << "\n";
|
file << " fullscreen: " << boolToString(video.fullscreen) << "\n";
|
||||||
file << " vsync: " << boolToString(video.vsync) << "\n";
|
file << " vsync: " << boolToString(video.vsync) << "\n";
|
||||||
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
file << " presentation_mode: " << presentationModeToString(video.presentation_mode)
|
||||||
|
<< " # integer_scale | letterbox | stretched | overscan\n";
|
||||||
file << " scale_mode: " << static_cast<int>(video.scale_mode)
|
file << " scale_mode: " << static_cast<int>(video.scale_mode)
|
||||||
<< " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
|
<< " # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
|
||||||
<< static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
|
<< static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
|
||||||
|
|||||||
+16
-1
@@ -18,6 +18,16 @@
|
|||||||
|
|
||||||
namespace Options {
|
namespace Options {
|
||||||
|
|
||||||
|
// Modes de presentacio del canvas virtual a la finestra. Tot fullscreen i
|
||||||
|
// windowed amb zoom no-fit el respecta; en windowed amb zoom exacte (1x/2x/3x)
|
||||||
|
// l'efecte es null perque el canvas ja encaixa amb la finestra.
|
||||||
|
enum class PresentationMode : std::uint8_t {
|
||||||
|
INTEGER_SCALE, // Multiple enter (1x, 2x, 3x...), centrat, amb barres
|
||||||
|
LETTERBOX, // Mante aspect ratio, centrat, amb barres
|
||||||
|
STRETCHED, // Omple tota la finestra, deforma l'aspect ratio
|
||||||
|
OVERSCAN // Mante aspect ratio i omple la finestra retallant el contingut fora
|
||||||
|
};
|
||||||
|
|
||||||
struct Window {
|
struct Window {
|
||||||
std::string caption = Defaults::Window::CAPTION;
|
std::string caption = Defaults::Window::CAPTION;
|
||||||
int zoom = Defaults::Window::ZOOM;
|
int zoom = Defaults::Window::ZOOM;
|
||||||
@@ -42,11 +52,16 @@ namespace Options {
|
|||||||
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
|
SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
|
||||||
bool fullscreen = Defaults::Video::FULLSCREEN;
|
bool fullscreen = Defaults::Video::FULLSCREEN;
|
||||||
bool vsync = Defaults::Video::VSYNC;
|
bool vsync = Defaults::Video::VSYNC;
|
||||||
bool integer_scale = Defaults::Video::INTEGER_SCALE;
|
PresentationMode presentation_mode = PresentationMode::INTEGER_SCALE;
|
||||||
GPU gpu;
|
GPU gpu;
|
||||||
ShaderConfig shader;
|
ShaderConfig shader;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Conversions string <-> PresentationMode per a config.yaml i notificacions
|
||||||
|
auto presentationModeToString(PresentationMode m) -> const char *;
|
||||||
|
auto presentationModeFromString(const std::string &s) -> PresentationMode;
|
||||||
|
auto nextPresentationMode(PresentationMode m) -> PresentationMode;
|
||||||
|
|
||||||
struct Music {
|
struct Music {
|
||||||
bool enabled = Defaults::Music::ENABLED;
|
bool enabled = Defaults::Music::ENABLED;
|
||||||
float volume = Defaults::Music::VOLUME;
|
float volume = Defaults::Music::VOLUME;
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ void Instructions::checkInput() {
|
|||||||
// pulsació; el quit es propaga via Director::iterate.
|
// pulsació; el quit es propaga via Director::iterate.
|
||||||
if (GlobalInputs::handle()) { return; }
|
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)) {
|
if (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)) {
|
||||||
if (mode_ == Mode::AUTO) {
|
if (mode_ == Mode::AUTO) {
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ void Intro::checkInput() {
|
|||||||
// pulsació; el quit es propaga via Director::iterate.
|
// pulsació; el quit es propaga via Director::iterate.
|
||||||
if (GlobalInputs::handle()) { return; }
|
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)) {
|
if (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)) {
|
||||||
Audio::get()->stopMusic();
|
Audio::get()->stopMusic();
|
||||||
section_->name = SECTION_PROG_TITLE;
|
section_->name = SECTION_PROG_TITLE;
|
||||||
section_->subsection = SUBSECTION_TITLE_1;
|
section_->subsection = SUBSECTION_TITLE_1;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ void Logo::checkInput() {
|
|||||||
// pulsació; el quit es propaga via Director::iterate.
|
// pulsació; el quit es propaga via Director::iterate.
|
||||||
if (GlobalInputs::handle()) { return; }
|
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)) {
|
if (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)) {
|
||||||
section_->name = SECTION_PROG_TITLE;
|
section_->name = SECTION_PROG_TITLE;
|
||||||
section_->subsection = SUBSECTION_TITLE_1;
|
section_->subsection = SUBSECTION_TITLE_1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -952,7 +952,16 @@ void Title::handleEvent(const SDL_Event *event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section_->subsection == SUBSECTION_TITLE_3) {
|
if (section_->subsection == SUBSECTION_TITLE_3) {
|
||||||
if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
|
// Activa menu i reinicia el countdown de demo nomes amb tecles "humanes".
|
||||||
|
// F1-F12 i ESC son hotkeys globals (zoom, fullscreen, shaders, exit, version)
|
||||||
|
// i no han d'interferir amb el flux de l'UI del titol.
|
||||||
|
bool human_input = (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP);
|
||||||
|
if (event->type == SDL_EVENT_KEY_UP) {
|
||||||
|
const SDL_Scancode S = event->key.scancode;
|
||||||
|
const bool IS_RESERVED = (S == SDL_SCANCODE_ESCAPE) || (S >= SDL_SCANCODE_F1 && S <= SDL_SCANCODE_F12);
|
||||||
|
human_input = !IS_RESERVED;
|
||||||
|
}
|
||||||
|
if (human_input) {
|
||||||
menu_visible_ = true;
|
menu_visible_ = true;
|
||||||
demo_remaining_s_ = DEMO_TIMEOUT_S;
|
demo_remaining_s_ = DEMO_TIMEOUT_S;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Title {
|
|||||||
void handleEvent(const SDL_Event *event); // Procesa un evento
|
void handleEvent(const SDL_Event *event); // Procesa un evento
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr const char *COPYRIGHT = "@2020 JailDesigner (v2.3.4)";
|
static constexpr const char *COPYRIGHT = "@2020 JailDesigner";
|
||||||
// Time-based: temps màxim a la pantalla del títol abans de tornar al
|
// Time-based: temps màxim a la pantalla del títol abans de tornar al
|
||||||
// logo o llançar el demo. 800 frames a 60Hz ⇒ 13.333 s.
|
// logo o llançar el demo. 800 frames a 60Hz ⇒ 13.333 s.
|
||||||
static constexpr float DEMO_TIMEOUT_S = 13.333F;
|
static constexpr float DEMO_TIMEOUT_S = 13.333F;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Texts {
|
namespace Texts {
|
||||||
constexpr const char* VERSION = "2.3.4"; // Versión del juego (también usada por el Makefile)
|
constexpr const char* VERSION = "2.4.0"; // Font de veritat: tambe la usen el Makefile (release names) i CMakeLists (project VERSION)
|
||||||
} // namespace Texts
|
} // namespace Texts
|
||||||
|
|||||||
Reference in New Issue
Block a user