Files
orni-attack/source/core/rendering/sdl_manager.cpp
T
JailDesigner bf83f161b0 Fase 1e: cierre de naming sweep (#pragma once, locals, comentarios castellano)
Tres tareas de pulido para cerrar la Fase 1 por completo:

#pragma once uniforme:
- sdl_manager.hpp y game_scene.hpp pasan de #ifndef/#define guards
  a #pragma once. Los archivos externos (stb_vorbis.h, fkyaml_node.hpp)
  se mantienen intactos (codigo de terceros).

Variables locales y parametros restantes (catalan -> ingles):
- fitxer -> file, moviment -> movement, inici -> start
- comptador -> counter, escalada -> scaled
- missatges -> messages, llista -> list
- alçada -> height, amplada -> width, llargada -> length
- origen -> origin, distancia -> distance, valor -> value, desti -> target
- neteja -> clear, presenta -> present (SDLManager)
- total_enemics -> total_enemies, configurar -> configure, iniciar -> start

Comentarios catalan -> castellano:
- Cabeceras de fichero actualizadas con nombres nuevos
  (escena_joc.hpp -> game_scene.hpp, etc.)
- Palabras tecnicas: trasllacio->traslacion, col-lisio->colision,
  inicialitzacio->inicializacion, posicio->posicion, rotacio->rotacion,
  velocitat->velocidad, acceleracio->aceleracion, explosio->explosion,
  renderitzat->renderizado, calcul->calculo, transicio->transicion,
  comprovacio->comprobacion, substitucio->sustitucion,
  utilitzacio->utilizacion, opcio->opcion, configuracio->configuracion,
  funcio->funcion, distancia, animacio->animacion
- Determinantes y conectores: aquest->este, aquesta->esta,
  amb->con, sense->sin, pero->pero, mai->nunca, nomes->solo,
  tambe->tambien, sempre->siempre, ja->ya, mateix->mismo,
  vegada->vez, dintre->dentro, fora->fuera, dreta->derecha,
  esquerra->izquierda, sortir->salir, sortida->salida,
  petit->pequeno, gran->grande, nou->nuevo, vell->viejo,
  molt->mucho, els->los, les->las, totes les->todas las,
  d'->de, com->como, quan->cuando, mentre->mientras,
  despres->despues, abans->antes, durant->durante, fins->hasta,
  encara->aun, llavors->entonces, aixi->asi, perque->porque
- Sustantivos: classe->clase, metode->metodo, parametre->parametro,
  versio->version, entitat->entidad, joc->juego, nivell->nivel,
  enemic->enemigo, naus->naves, bales->balas, fitxer->archivo,
  pentagon->pentagono, pun- tuacio->puntuacion, flotant->flotante,
  titol->titulo, objectiu->objetivo, mostra->muestra, tipus->tipo

Strings literales preservados en valenciano segun decision del
usuario: el texto del HUD del juego (puntuaciones, mensajes en
pantalla, archivo de config) se mantiene en valenciano original.

70 fitxers tocats, +1117 / -1123. Compila i enllaca.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:12:30 +02:00

482 lines
15 KiB
C++

// sdl_manager.cpp - Implementació del gestor SDL3
// © 2025 Port a C++20 con SDL3
#include "sdl_manager.hpp"
#include <algorithm>
#include <format>
#include <iostream>
#include "core/defaults.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/coordinate_transform.hpp"
#include "core/rendering/line_renderer.hpp"
#include "game/options.hpp"
#include "project.h"
SDLManager::SDLManager()
: finestra_(nullptr),
renderer_(nullptr),
fps_accumulator_(0.0F),
fps_frame_count_(0),
fps_display_(0),
current_width_(Defaults::Window::WIDTH),
current_height_(Defaults::Window::HEIGHT),
is_fullscreen_(false),
max_width_(1920),
max_height_(1080),
zoom_factor_(Defaults::Window::BASE_ZOOM),
windowed_width_(Defaults::Window::WIDTH),
windowed_height_(Defaults::Window::HEIGHT),
max_zoom_(1.0F) {
// Inicialitzar SDL3
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
return;
}
// Calcular mida màxima des del display
calculateMaxWindowSize();
// Construir título dinàmic
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
// Crear finestra CENTRADA (SDL ho hace automàticament con CENTERED)
finestra_ =
SDL_CreateWindow(window_title.c_str(), current_width_, current_height_,
SDL_WINDOW_RESIZABLE // Permetre resize manual también
);
if (finestra_ == nullptr) {
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
SDL_Quit();
return;
}
// IMPORTANT: Centrar explícitament la finestra
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Crear renderer con aceleración
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
if (renderer_ == nullptr) {
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
SDL_DestroyWindow(finestra_);
SDL_Quit();
return;
}
// Aplicar configuración de V-Sync
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
// CRÍTIC: Configurar viewport scaling
updateLogicalPresentation();
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
<< " (logic: " << Defaults::Game::WIDTH << "x"
<< Defaults::Game::HEIGHT << ")" << '\n';
}
// Constructor con configuración
SDLManager::SDLManager(int width, int height, bool fullscreen)
: finestra_(nullptr),
renderer_(nullptr),
fps_accumulator_(0.0F),
fps_frame_count_(0),
fps_display_(0),
current_width_(width),
current_height_(height),
is_fullscreen_(fullscreen),
max_width_(1920),
max_height_(1080),
zoom_factor_(static_cast<float>(width) / Defaults::Window::WIDTH),
windowed_width_(width),
windowed_height_(height),
max_zoom_(1.0F) {
// Inicialitzar SDL3
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
return;
}
// Calcular mida màxima des del display
calculateMaxWindowSize();
// Construir título dinàmic
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
// Configurar flags de la finestra
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
if (is_fullscreen_) {
flags = static_cast<SDL_WindowFlags>(flags | SDL_WINDOW_FULLSCREEN);
}
// Crear finestra
finestra_ = SDL_CreateWindow(window_title.c_str(), current_width_, current_height_, flags);
if (finestra_ == nullptr) {
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
SDL_Quit();
return;
}
// Centrar explícitament la finestra (si no es fullscreen)
if (!is_fullscreen_) {
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Crear renderer con aceleración
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
if (renderer_ == nullptr) {
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
SDL_DestroyWindow(finestra_);
SDL_Quit();
return;
}
// Aplicar configuración de V-Sync
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
// Configurar viewport scaling
updateLogicalPresentation();
// Inicialitzar sistema de cursor
// En fullscreen: forzar ocultació permanent
if (is_fullscreen_) {
Mouse::setForceHidden(true);
}
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
<< " (logic: " << Defaults::Game::WIDTH << "x"
<< Defaults::Game::HEIGHT << ")";
if (is_fullscreen_) {
std::cout << " [FULLSCREEN]";
}
std::cout << '\n';
}
SDLManager::~SDLManager() {
if (renderer_ != nullptr) {
SDL_DestroyRenderer(renderer_);
renderer_ = nullptr;
}
if (finestra_ != nullptr) {
SDL_DestroyWindow(finestra_);
finestra_ = nullptr;
}
SDL_Quit();
std::cout << "SDL3 netejat correctament" << '\n';
}
void SDLManager::calculateMaxWindowSize() {
SDL_DisplayID display = SDL_GetPrimaryDisplay();
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
if (mode != nullptr) {
// Deixar marge de 100px para decoracions de l'OS
max_width_ = mode->w - 100;
max_height_ = mode->h - 100;
std::cout << "Display detectat: " << mode->w << "x" << mode->h
<< " (max finestra: " << max_width_ << "x" << max_height_ << ")"
<< '\n';
} else {
// Fallback conservador
max_width_ = 1920;
max_height_ = 1080;
std::cerr << "No s'ha pogut detectar el display, usant fallback: "
<< max_width_ << "x" << max_height_ << '\n';
}
// Calculate max zoom immediately after determining max size
calculateMaxZoom();
}
void SDLManager::calculateMaxZoom() {
// Maximum zoom limited by BOTH width and height (preserves 4:3)
float max_zoom_width = static_cast<float>(max_width_) / Defaults::Window::WIDTH;
float max_zoom_height = static_cast<float>(max_height_) / Defaults::Window::HEIGHT;
// Take smaller constraint
float max_zoom_unrounded = std::min(max_zoom_width, max_zoom_height);
// Round DOWN to nearest 0.1 increment (user preference)
max_zoom_ = std::floor(max_zoom_unrounded / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
// Safety clamp
max_zoom_ = std::max(max_zoom_, Defaults::Window::MIN_ZOOM);
std::cout << "Max zoom: " << max_zoom_ << "x (display: "
<< max_width_ << "x" << max_height_ << ")" << '\n';
}
void SDLManager::applyZoom(float new_zoom) {
// Clamp to valid range
new_zoom = std::max(Defaults::Window::MIN_ZOOM,
std::min(new_zoom, max_zoom_));
// Round to nearest 0.1 increment
new_zoom = std::round(new_zoom / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
// No change?
if (std::abs(new_zoom - zoom_factor_) < 0.01F) {
return;
}
zoom_factor_ = new_zoom;
// Calculate physical dimensions (4:3 maintained automatically)
int new_width = static_cast<int>(std::round(
Defaults::Window::WIDTH * zoom_factor_));
int new_height = static_cast<int>(std::round(
Defaults::Window::HEIGHT * zoom_factor_));
// Apply to window (centers via applyWindowSize)
applyWindowSize(new_width, new_height);
// Update viewport for new zoom
updateViewport();
// Update windowed size cache
windowed_width_ = new_width;
windowed_height_ = new_height;
// Persist
Options::window.width = new_width;
Options::window.height = new_height;
Options::window.zoom_factor = zoom_factor_;
std::cout << "Zoom: " << zoom_factor_ << "x ("
<< new_width << "x" << new_height << ")" << '\n';
}
void SDLManager::updateLogicalPresentation() {
// CANVIAT: Ya no usem SDL_SetRenderLogicalPresentation
// Ara renderitzem directament a resolució física per evitar pixelació irregular
// El viewport con letterbox es configura a updateViewport()
updateViewport();
}
void SDLManager::updateViewport() {
// Calcular dimensions físiques basades en el zoom
float scale = zoom_factor_;
int scaled_width = static_cast<int>(std::round(Defaults::Game::WIDTH * scale));
int scaled_height = static_cast<int>(std::round(Defaults::Game::HEIGHT * scale));
// Cálculo de letterbox (centrar l'àrea scaled)
int offset_x = (current_width_ - scaled_width) / 2;
int offset_y = (current_height_ - scaled_height) / 2;
// Evitar offsets negatius
offset_x = std::max(offset_x, 0);
offset_y = std::max(offset_y, 0);
// Configurar viewport per al renderizado
SDL_Rect viewport = {offset_x, offset_y, scaled_width, scaled_height};
SDL_SetRenderViewport(renderer_, &viewport);
std::cout << "Viewport: " << scaled_width << "x" << scaled_height
<< " @ (" << offset_x << "," << offset_y << ") [scale=" << scale << "]"
<< '\n';
}
void SDLManager::updateRenderingContext() const {
// Actualitzar el factor de scale global para todas las funciones de renderizado
Rendering::g_current_scale_factor = zoom_factor_;
}
void SDLManager::increaseWindowSize() {
if (is_fullscreen_) {
return;
}
float new_zoom = zoom_factor_ + Defaults::Window::ZOOM_INCREMENT;
applyZoom(new_zoom);
std::cout << "F2: Zoom aumentat a " << zoom_factor_ << "x" << '\n';
}
void SDLManager::decreaseWindowSize() {
if (is_fullscreen_) {
return;
}
float new_zoom = zoom_factor_ - Defaults::Window::ZOOM_INCREMENT;
applyZoom(new_zoom);
std::cout << "F1: Zoom reduït a " << zoom_factor_ << "x" << '\n';
}
void SDLManager::applyWindowSize(int new_width, int new_height) {
// Obtenir posición actual ABANS del resize
int old_x;
int old_y;
SDL_GetWindowPosition(finestra_, &old_x, &old_y);
int old_width = current_width_;
int old_height = current_height_;
// Actualitzar mida
SDL_SetWindowSize(finestra_, new_width, new_height);
current_width_ = new_width;
current_height_ = new_height;
// CENTRADO INTEL·LIGENT (algoritme de pollo)
// Calcular nueva posición per mantenir la finestra centrada sobre si misma
int delta_width = old_width - new_width;
int delta_height = old_height - new_height;
int new_x = old_x + (delta_width / 2);
int new_y = old_y + (delta_height / 2);
// Evitar que la finestra surti de la pantalla
constexpr int TITLEBAR_HEIGHT = 35; // Alçada aproximada de la barra de título
new_x = std::max(new_x, 0);
new_y = std::max(new_y, TITLEBAR_HEIGHT);
SDL_SetWindowPosition(finestra_, new_x, new_y);
// Actualitzar viewport después del resize
updateViewport();
}
void SDLManager::toggleFullscreen() {
if (!is_fullscreen_) {
// ENTERING FULLSCREEN
windowed_width_ = current_width_;
windowed_height_ = current_height_;
is_fullscreen_ = true;
SDL_SetWindowFullscreen(finestra_, true);
std::cout << "F3: Fullscreen activat (guardada: "
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
} else {
// EXITING FULLSCREEN
is_fullscreen_ = false;
SDL_SetWindowFullscreen(finestra_, false);
// CRITICAL: Explicitly restore windowed size
applyWindowSize(windowed_width_, windowed_height_);
std::cout << "F3: Fullscreen desactivat (restaurada: "
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
}
Options::window.fullscreen = is_fullscreen_;
// Notificar al mòdul Mouse: Fullscreen requereix ocultació permanent del cursor.
// Cuando es surt de fullscreen, restaurar el comportament normal de auto-ocultació.
Mouse::setForceHidden(is_fullscreen_);
}
bool SDLManager::handleWindowEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
SDL_GetWindowSize(finestra_, &current_width_, &current_height_);
// Calculate zoom from actual size (may not align to 0.1 increments)
float new_zoom = static_cast<float>(current_width_) / Defaults::Window::WIDTH;
zoom_factor_ = std::max(Defaults::Window::MIN_ZOOM,
std::min(new_zoom, max_zoom_));
// Update windowed cache (if not in fullscreen)
if (!is_fullscreen_) {
windowed_width_ = current_width_;
windowed_height_ = current_height_;
}
// Actualitzar viewport después del resize manual
updateViewport();
std::cout << "Finestra redimensionada: " << current_width_
<< "x" << current_height_ << " (zoom ≈" << zoom_factor_ << "x)"
<< '\n';
return true;
}
return false;
}
void SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) {
if (renderer_ == nullptr) {
return;
}
// [MODIFICAT] Usar color oscil·lat del fons en lloc dels parámetros
(void)r;
(void)g;
(void)b; // Suprimir warnings
SDL_Color bg = color_oscillator_.getCurrentBackgroundColor();
SDL_SetRenderDrawColor(renderer_, bg.r, bg.g, bg.b, 255);
SDL_RenderClear(renderer_);
}
void SDLManager::present() {
if (renderer_ == nullptr) {
return;
}
SDL_RenderPresent(renderer_);
}
// [NUEVO] Actualitzar colors con oscil·lació
void SDLManager::updateColors(float delta_time) {
color_oscillator_.update(delta_time);
// Actualitzar color global de línies
Rendering::setLineColor(color_oscillator_.getCurrentLineColor());
}
// [NUEVO] Actualitzar counter de FPS
void SDLManager::updateFPS(float delta_time) {
// Acumular time i frames
fps_accumulator_ += delta_time;
fps_frame_count_++;
// Actualitzar display cada 0.5 segons
if (fps_accumulator_ >= 0.5F) {
fps_display_ = static_cast<int>(fps_frame_count_ / fps_accumulator_);
fps_frame_count_ = 0;
fps_accumulator_ = 0.0F;
// Actualitzar título de la finestra
std::string vsync_state = (Options::rendering.vsync == 1) ? "ON" : "OFF";
std::string title = std::format("{} v{} ({}) - {} FPS - VSync: {}",
Project::LONG_NAME,
Project::VERSION,
Project::COPYRIGHT,
fps_display_,
vsync_state);
if (finestra_ != nullptr) {
SDL_SetWindowTitle(finestra_, title.c_str());
}
}
}
// [NUEVO] Actualitzar título de la finestra
void SDLManager::setWindowTitle(const std::string& title) {
if (finestra_ != nullptr) {
SDL_SetWindowTitle(finestra_, title.c_str());
}
}
// [NUEVO] Toggle V-Sync (F4)
void SDLManager::toggleVSync() {
// Toggle: 1 → 0 → 1
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
// Aplicar a SDL
if (renderer_ != nullptr) {
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
}
// Reset FPS counter para evitar valores mixtos entre regímenes
fps_accumulator_ = 0.0F;
fps_frame_count_ = 0;
// Guardar configuración
Options::saveToFile();
}