460 lines
14 KiB
C++
460 lines
14 KiB
C++
// sdl_manager.cpp - Implementació del gestor SDL3
|
|
// © 2025 Port a C++20 amb SDL3
|
|
|
|
#include "sdl_manager.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <format>
|
|
#include <iostream>
|
|
|
|
#include "core/defaults.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() << std::endl;
|
|
return;
|
|
}
|
|
|
|
// Calcular mida màxima des del display
|
|
calculateMaxWindowSize();
|
|
|
|
// Construir títol dinàmic
|
|
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
|
|
|
// Crear finestra CENTRADA (SDL ho fa automàticament amb CENTERED)
|
|
finestra_ =
|
|
SDL_CreateWindow(window_title.c_str(), current_width_, current_height_,
|
|
SDL_WINDOW_RESIZABLE // Permetre resize manual també
|
|
);
|
|
|
|
if (!finestra_) {
|
|
std::cerr << "Error creant finestra: " << SDL_GetError() << std::endl;
|
|
SDL_Quit();
|
|
return;
|
|
}
|
|
|
|
// IMPORTANT: Centrar explícitament la finestra
|
|
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
|
|
// Crear renderer amb acceleració
|
|
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
|
|
|
if (!renderer_) {
|
|
std::cerr << "Error creant renderer: " << SDL_GetError() << std::endl;
|
|
SDL_DestroyWindow(finestra_);
|
|
SDL_Quit();
|
|
return;
|
|
}
|
|
|
|
// Aplicar configuració 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 << ")" << std::endl;
|
|
}
|
|
|
|
// Constructor amb configuració
|
|
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() << std::endl;
|
|
return;
|
|
}
|
|
|
|
// Calcular mida màxima des del display
|
|
calculateMaxWindowSize();
|
|
|
|
// Construir títol 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_) {
|
|
std::cerr << "Error creant finestra: " << SDL_GetError() << std::endl;
|
|
SDL_Quit();
|
|
return;
|
|
}
|
|
|
|
// Centrar explícitament la finestra (si no és fullscreen)
|
|
if (!is_fullscreen_) {
|
|
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
}
|
|
|
|
// Crear renderer amb acceleració
|
|
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
|
|
|
if (!renderer_) {
|
|
std::cerr << "Error creant renderer: " << SDL_GetError() << std::endl;
|
|
SDL_DestroyWindow(finestra_);
|
|
SDL_Quit();
|
|
return;
|
|
}
|
|
|
|
// Aplicar configuració de V-Sync
|
|
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
|
|
|
// Configurar viewport scaling
|
|
updateLogicalPresentation();
|
|
|
|
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 << std::endl;
|
|
}
|
|
|
|
SDLManager::~SDLManager() {
|
|
if (renderer_) {
|
|
SDL_DestroyRenderer(renderer_);
|
|
renderer_ = nullptr;
|
|
}
|
|
|
|
if (finestra_) {
|
|
SDL_DestroyWindow(finestra_);
|
|
finestra_ = nullptr;
|
|
}
|
|
|
|
SDL_Quit();
|
|
std::cout << "SDL3 netejat correctament" << std::endl;
|
|
}
|
|
|
|
void SDLManager::calculateMaxWindowSize() {
|
|
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
|
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
|
|
|
|
if (mode) {
|
|
// Deixar marge de 100px per a 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_ << ")"
|
|
<< std::endl;
|
|
} 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_ << std::endl;
|
|
}
|
|
|
|
// 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_ << ")" << std::endl;
|
|
}
|
|
|
|
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 << ")" << std::endl;
|
|
}
|
|
|
|
void SDLManager::updateLogicalPresentation() {
|
|
// CANVIAT: Ja no usem SDL_SetRenderLogicalPresentation
|
|
// Ara renderitzem directament a resolució física per evitar pixelació irregular
|
|
// El viewport amb 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àlcul de letterbox (centrar l'àrea escalada)
|
|
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 renderitzat
|
|
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 << "]"
|
|
<< std::endl;
|
|
}
|
|
|
|
void SDLManager::updateRenderingContext() {
|
|
// Actualitzar el factor d'escala global per a totes les funcions de renderitzat
|
|
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" << std::endl;
|
|
}
|
|
|
|
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" << std::endl;
|
|
}
|
|
|
|
void SDLManager::applyWindowSize(int new_width, int new_height) {
|
|
// Obtenir posició actual ABANS del resize
|
|
int old_x, 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 nova posició per mantenir la finestra centrada sobre si mateixa
|
|
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ítol
|
|
new_x = std::max(new_x, 0);
|
|
new_y = std::max(new_y, TITLEBAR_HEIGHT);
|
|
|
|
SDL_SetWindowPosition(finestra_, new_x, new_y);
|
|
|
|
// Actualitzar viewport despré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_ << ")" << std::endl;
|
|
} 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_ << ")" << std::endl;
|
|
}
|
|
|
|
Options::window.fullscreen = is_fullscreen_;
|
|
}
|
|
|
|
bool SDLManager::handleWindowEvent(const SDL_Event& event) {
|
|
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
|
SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_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 després del resize manual
|
|
updateViewport();
|
|
|
|
std::cout << "Finestra redimensionada: " << current_width_
|
|
<< "x" << current_height_ << " (zoom ≈" << zoom_factor_ << "x)"
|
|
<< std::endl;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SDLManager::neteja(uint8_t r, uint8_t g, uint8_t b) {
|
|
if (!renderer_)
|
|
return;
|
|
|
|
// [MODIFICAT] Usar color oscil·lat del fons en lloc dels paràmetres
|
|
(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::presenta() {
|
|
if (!renderer_)
|
|
return;
|
|
|
|
SDL_RenderPresent(renderer_);
|
|
}
|
|
|
|
// [NUEVO] Actualitzar colors amb 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 comptador de FPS
|
|
void SDLManager::updateFPS(float delta_time) {
|
|
// Acumular temps 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ítol de la finestra
|
|
std::string title = std::format("{} v{} ({}) - {} FPS",
|
|
Project::LONG_NAME,
|
|
Project::VERSION,
|
|
Project::COPYRIGHT,
|
|
fps_display_);
|
|
|
|
if (finestra_) {
|
|
SDL_SetWindowTitle(finestra_, title.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NUEVO] Actualitzar títol de la finestra
|
|
void SDLManager::setWindowTitle(const std::string& title) {
|
|
if (finestra_) {
|
|
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_) {
|
|
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
|
}
|
|
|
|
// Guardar configuració
|
|
Options::saveToFile();
|
|
}
|