#include "window_manager.h" #include // for SDL_Init #include // for SDL_GetError #include // for SDL_CreateWindow, SDL_GetDisplayBounds #include // for cout // Incluir backends específicos // TODO: Reactivar cuando se implemente compilación Objective-C++ // #ifdef __APPLE__ // #include "backends/metal_renderer.h" // #endif #if defined(_WIN32) || defined(__linux__) #include "backends/vulkan_renderer.h" #endif // Fallback SDL siempre disponible #include "backends/sdl_renderer.h" namespace vibe4 { WindowManager::WindowManager() = default; WindowManager::~WindowManager() { shutdown(); } bool WindowManager::initialize(const char* title, int width, int height, int zoom) { logical_width_ = width; logical_height_ = height; current_zoom_ = zoom; // Inicializar SDL if (!SDL_Init(SDL_INIT_VIDEO)) { std::cout << "¡SDL no se pudo inicializar! Error: " << SDL_GetError() << std::endl; return false; } // Crear ventana SDL (sin multiplicar por zoom - trabajamos nativo) if (!createSDLWindow(title, width, height)) { return false; } // Detectar y crear el mejor backend disponible BackendType backend_type = detectBestBackend(); renderer_ = createRenderer(backend_type); if (!renderer_) { std::cout << "¡No se pudo crear ningún backend de renderizado!" << std::endl; return false; } // Inicializar el renderer if (!renderer_->initialize(window_, width, height)) { std::cout << "¡No se pudo inicializar el backend " << renderer_->getBackendName() << "!" << std::endl; return false; } // Crear textura de renderizado para postprocesado auto* sdl_renderer = getSDLRenderer(); if (sdl_renderer) { render_texture_ = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height); if (!render_texture_) { std::cout << "¡No se pudo crear la textura de renderizado! Error: " << SDL_GetError() << std::endl; return false; } // Configurar filtro nearest neighbor para píxel perfect SDL_SetTextureScaleMode(render_texture_, SDL_SCALEMODE_NEAREST); std::cout << "Textura de renderizado creada: " << width << "x" << height << std::endl; } std::cout << "Backend de renderizado inicializado: " << renderer_->getBackendName() << std::endl; return true; } void WindowManager::shutdown() { if (render_texture_) { SDL_DestroyTexture(render_texture_); render_texture_ = nullptr; } if (renderer_) { renderer_->shutdown(); renderer_.reset(); } if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; } SDL_Quit(); } bool WindowManager::createSDLWindow(const char* title, int width, int height) { Uint32 window_flags = SDL_WINDOW_OPENGL; // Empezamos con OpenGL como base // Agregar flags específicos dependiendo del backend que vayamos a usar BackendType backend_type = detectBestBackend(); switch (backend_type) { case BackendType::METAL: window_flags = SDL_WINDOW_METAL; break; case BackendType::VULKAN: window_flags = SDL_WINDOW_VULKAN; break; case BackendType::SDL: default: window_flags = SDL_WINDOW_OPENGL; break; } window_ = SDL_CreateWindow(title, width * current_zoom_, height * current_zoom_, window_flags); if (!window_) { std::cout << "¡No se pudo crear la ventana! Error: " << SDL_GetError() << std::endl; return false; } return true; } BackendType WindowManager::detectBestBackend() const { // TODO: Reactivar Metal cuando se implemente compilación Objective-C++ #ifdef __APPLE__ return BackendType::SDL; // Temporalmente usar SDL en macOS #elif defined(_WIN32) || defined(__linux__) return BackendType::VULKAN; // Windows/Linux usan Vulkan #else return BackendType::SDL; // Fallback para otras plataformas #endif } std::unique_ptr WindowManager::createRenderer(BackendType type) { switch (type) { // TODO: Reactivar cuando se implemente compilación Objective-C++ // #ifdef __APPLE__ // case BackendType::METAL: // return std::make_unique(); // #endif #if defined(_WIN32) || defined(__linux__) case BackendType::VULKAN: return std::make_unique(); #endif case BackendType::SDL: default: return std::make_unique(); } } void WindowManager::setTitle(const char* title) { if (window_) { SDL_SetWindowTitle(window_, title); } } bool WindowManager::setFullscreen(bool enable) { if (!window_) return false; bool result = SDL_SetWindowFullscreen(window_, enable); if (result) { fullscreen_enabled_ = enable; if (enable) { real_fullscreen_enabled_ = false; // Solo uno puede estar activo } } return result; } bool WindowManager::setRealFullscreen(bool enable) { if (!window_) return false; bool result = SDL_SetWindowFullscreen(window_, enable); if (result) { real_fullscreen_enabled_ = enable; if (enable) { fullscreen_enabled_ = false; // Solo uno puede estar activo } } return result; } void WindowManager::setZoom(int new_zoom) { // Validar zoom usando el cálculo dinámico del original int max_zoom = calculateMaxZoom(); new_zoom = std::max(MIN_ZOOM, std::min(new_zoom, max_zoom)); if (new_zoom == current_zoom_) { return; // No hay cambio } // Obtener posición actual del centro de la ventana (lógica del original) int current_x, current_y; SDL_GetWindowPosition(window_, ¤t_x, ¤t_y); int current_center_x = current_x + (logical_width_ * current_zoom_) / 2; int current_center_y = current_y + (logical_height_ * current_zoom_) / 2; // Calcular nuevo tamaño int new_width = logical_width_ * new_zoom; int new_height = logical_height_ * new_zoom; // Calcular nueva posición (centrada en el punto actual) int new_x = current_center_x - new_width / 2; int new_y = current_center_y - new_height / 2; // Obtener límites del escritorio para no salirse (con fallback robusto) SDL_Rect display_bounds; bool bounds_success = false; // Intentar primero el display de la ventana, luego el primario SDL_DisplayID display_id = SDL_GetDisplayForWindow(window_); if (display_id != 0) { bounds_success = (SDL_GetDisplayBounds(display_id, &display_bounds) == 0); } if (!bounds_success) { // Fallback al display primario como en el original bounds_success = (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display_bounds) == 0); } if (!bounds_success) { // Último fallback: valores seguros para resolución común display_bounds.w = 1920; display_bounds.h = 1080; bounds_success = true; } if (bounds_success) { // Aplicar márgenes int min_x = DESKTOP_MARGIN; int min_y = DESKTOP_MARGIN; int max_x = display_bounds.w - new_width - DESKTOP_MARGIN; int max_y = display_bounds.h - new_height - DESKTOP_MARGIN - DECORATION_HEIGHT; // Limitar posición new_x = std::max(min_x, std::min(new_x, max_x)); new_y = std::max(min_y, std::min(new_y, max_y)); } // Aplicar cambios SDL_SetWindowSize(window_, new_width, new_height); SDL_SetWindowPosition(window_, new_x, new_y); current_zoom_ = new_zoom; // Notificar al renderer del cambio (mantener resolución lógica) if (renderer_) { renderer_->resize(logical_width_, logical_height_); } } void WindowManager::updateWindowSize() { // Simplificar: usar setZoom que ya maneja todo el centrado y guardas setZoom(current_zoom_); } int WindowManager::calculateMaxZoom() const { // Obtener información del display usando el método del commit original int num_displays = 0; SDL_DisplayID *displays = SDL_GetDisplays(&num_displays); if (displays == nullptr || num_displays == 0) { return MIN_ZOOM; // Fallback si no se puede obtener } // Obtener el modo de display actual const auto *dm = SDL_GetCurrentDisplayMode(displays[0]); if (dm == nullptr) { SDL_free(displays); return MIN_ZOOM; } // Calcular zoom máximo usando la fórmula de Coffee Crisis del original const int MAX_ZOOM_CALC = std::min(dm->w / logical_width_, (dm->h - DECORATION_HEIGHT) / logical_height_); SDL_free(displays); // Aplicar límites return std::max(MIN_ZOOM, std::min(MAX_ZOOM_CALC, MAX_ZOOM)); } void WindowManager::zoomIn() { int max_zoom = calculateMaxZoom(); if (current_zoom_ < max_zoom) { setZoom(current_zoom_ + 1); } } void WindowManager::zoomOut() { if (current_zoom_ > MIN_ZOOM) { setZoom(current_zoom_ - 1); } } void WindowManager::getSize(int& width, int& height) const { if (window_) { SDL_GetWindowSize(window_, &width, &height); } else { width = height = 0; } } void WindowManager::getLogicalSize(int& width, int& height) const { width = logical_width_; height = logical_height_; } BackendType WindowManager::getBackendType() const { return renderer_ ? renderer_->getBackendType() : BackendType::SDL; } const char* WindowManager::getBackendName() const { return renderer_ ? renderer_->getBackendName() : "None"; } SDL_Renderer* WindowManager::getSDLRenderer() const { // Solo funciona si el backend activo es SDL if (renderer_ && renderer_->getBackendType() == BackendType::SDL) { auto* sdl_renderer = static_cast(renderer_.get()); return sdl_renderer->getSDLRenderer(); } return nullptr; } bool WindowManager::setRenderTarget() { auto* sdl_renderer = getSDLRenderer(); if (!sdl_renderer || !render_texture_) { return false; } // Cambiar el render target a nuestra textura if (!SDL_SetRenderTarget(sdl_renderer, render_texture_)) { std::cout << "¡No se pudo establecer render target! Error: " << SDL_GetError() << std::endl; return false; } // Limpiar la textura de renderizado SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255); SDL_RenderClear(sdl_renderer); return true; } void WindowManager::presentFrame() { auto* sdl_renderer = getSDLRenderer(); if (!sdl_renderer || !render_texture_) { return; } // Volver al render target por defecto (la ventana) SDL_SetRenderTarget(sdl_renderer, nullptr); // Limpiar la pantalla SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255); SDL_RenderClear(sdl_renderer); // Copiar la textura de renderizado a la pantalla 1:1 (sin zoom) SDL_RenderTexture(sdl_renderer, render_texture_, nullptr, nullptr); // Presentar el frame final SDL_RenderPresent(sdl_renderer); } } // namespace vibe4