// sdl_manager.cpp - Implementació del gestor SDL3 // © 2025 Port a C++20 amb SDL3 #include "sdl_manager.hpp" #include #include #include #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() << 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(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(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; // Inicialitzar mòdul Mouse amb l'estat actual de fullscreen Mouse::setForceHidden(is_fullscreen_); } 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(max_width_) / Defaults::Window::WIDTH; float max_zoom_height = static_cast(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(std::round( Defaults::Window::WIDTH * zoom_factor_)); int new_height = static_cast(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(std::round(Defaults::Game::WIDTH * scale)); int scaled_height = static_cast(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_; // Notificar al mòdul Mouse: Fullscreen requereix ocultació permanent del cursor. // Quan es surt de fullscreen, restaurar el comportament normal d'auto-ocultació. Mouse::setForceHidden(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(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(fps_frame_count_ / fps_accumulator_); fps_frame_count_ = 0; fps_accumulator_ = 0.0f; // Actualitzar títol 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_) { 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); } // Reset FPS counter para evitar valores mixtos entre regímenes fps_accumulator_ = 0.0f; fps_frame_count_ = 0; // Guardar configuració Options::saveToFile(); }