#include "ui_manager.hpp" #include #include #include #include "ball.hpp" // for Ball #include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode #include "engine.hpp" // for Engine (info de sistema) #include "scene/scene_manager.hpp" // for SceneManager #include "shapes/shape.hpp" // for Shape #include "text/textrenderer.hpp" // for TextRenderer #include "theme_manager.hpp" // for ThemeManager #include "notifier.hpp" // for Notifier #include "help_overlay.hpp" // for HelpOverlay // ============================================================================ // HELPER: Obtener viewport en coordenadas físicas (no lógicas) // ============================================================================ // SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación // lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar // temporalmente la presentación lógica. static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) { // Guardar estado actual de presentación lógica int logical_w = 0, logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode); // Deshabilitar presentación lógica temporalmente SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED); // Obtener viewport en coordenadas físicas (píxeles reales) SDL_Rect physical_viewport; SDL_GetRenderViewport(renderer, &physical_viewport); // Restaurar presentación lógica SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, presentation_mode); return physical_viewport; } UIManager::UIManager() : text_renderer_debug_(nullptr) , text_renderer_notifier_(nullptr) , notifier_(nullptr) , help_overlay_(nullptr) , show_debug_(false) , fps_last_time_(0) , fps_frame_count_(0) , fps_current_(0) , fps_text_("FPS: 0") , vsync_text_("VSYNC ON") , renderer_(nullptr) , theme_manager_(nullptr) , physical_window_width_(0) , physical_window_height_(0) , logical_window_width_(0) , logical_window_height_(0) , current_font_size_(18) { // Tamaño por defecto (medium) } UIManager::~UIManager() { // Limpieza: Los objetos creados con new deben ser eliminados delete text_renderer_debug_; delete text_renderer_notifier_; delete notifier_; delete help_overlay_; } void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, int physical_width, int physical_height, int logical_width, int logical_height) { delete text_renderer_debug_; text_renderer_debug_ = nullptr; delete text_renderer_notifier_; text_renderer_notifier_ = nullptr; delete notifier_; notifier_ = nullptr; delete help_overlay_; help_overlay_ = nullptr; renderer_ = renderer; theme_manager_ = theme_manager; physical_window_width_ = physical_width; physical_window_height_ = physical_height; logical_window_width_ = logical_width; logical_window_height_ = logical_height; // Calcular tamaño de fuente apropiado según dimensiones LÓGICAS (sin zoom) current_font_size_ = calculateFontSize(logical_height); // Crear renderers de texto text_renderer_debug_ = new TextRenderer(); text_renderer_notifier_ = new TextRenderer(); // Inicializar renderers con tamaño dinámico text_renderer_debug_->init(renderer, APP_FONT, std::max(9, current_font_size_ - 2), true); text_renderer_notifier_->init(renderer, APP_FONT, current_font_size_, true); // Crear y configurar sistema de notificaciones notifier_ = new Notifier(); notifier_->init(renderer, text_renderer_notifier_, theme_manager_, physical_width, physical_height); // Crear y configurar sistema de ayuda (overlay) help_overlay_ = new HelpOverlay(); help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, std::max(9, current_font_size_ - 1)); // Inicializar FPS counter fps_last_time_ = SDL_GetTicks(); fps_frame_count_ = 0; fps_current_ = 0; } void UIManager::update(Uint64 current_time, float delta_time) { // Calcular FPS fps_frame_count_++; if (current_time - fps_last_time_ >= 1000) { // Actualizar cada segundo fps_current_ = fps_frame_count_; fps_frame_count_ = 0; fps_last_time_ = current_time; fps_text_ = "FPS: " + std::to_string(fps_current_); } // Actualizar sistema de notificaciones notifier_->update(current_time); } void UIManager::render(SDL_Renderer* renderer, const Engine* engine, const SceneManager* scene_manager, SimulationMode current_mode, AppMode current_app_mode, const Shape* active_shape, float shape_convergence, int physical_width, int physical_height, int current_screen_width) { // Actualizar dimensiones físicas (puede cambiar en fullscreen) physical_window_width_ = physical_width; physical_window_height_ = physical_height; // Renderizar debug HUD si está activo if (show_debug_) { renderDebugHUD(engine, scene_manager, current_mode, current_app_mode, active_shape, shape_convergence); } // Renderizar notificaciones (siempre al final, sobre todo lo demás) notifier_->render(); // Renderizar ayuda (siempre última, sobre todo incluso notificaciones) if (help_overlay_) { help_overlay_->render(renderer); } } void UIManager::toggleDebug() { show_debug_ = !show_debug_; } void UIManager::toggleHelp() { if (help_overlay_) { help_overlay_->toggle(); } } void UIManager::showNotification(const std::string& text, Uint64 duration) { if (duration == 0) { duration = NOTIFICATION_DURATION; } notifier_->show(text, duration); } void UIManager::updateVSyncText(bool enabled) { vsync_text_ = enabled ? "V-Sync: On" : "V-Sync: Off"; } void UIManager::updatePhysicalWindowSize(int width, int height, int logical_height) { physical_window_width_ = width; physical_window_height_ = height; // Actualizar altura lógica si se proporciona (ej. al entrar/salir de F4) if (logical_height > 0) { logical_window_height_ = logical_height; } // Calcular nuevo tamaño de fuente apropiado basado en altura LÓGICA // (las dimensiones lógicas no cambian con zoom, solo con cambios explícitos de resolución) int new_font_size = calculateFontSize(logical_window_height_); // Si el tamaño cambió, reinicializar todos los text renderers if (new_font_size != current_font_size_) { current_font_size_ = new_font_size; // Reinicializar text renderers con nuevo tamaño if (text_renderer_debug_) { text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2)); } if (text_renderer_notifier_) { text_renderer_notifier_->reinitialize(current_font_size_); } } // Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente) if (help_overlay_) { help_overlay_->updateAll(std::max(9, current_font_size_ - 1), width, height); } // Actualizar otros componentes de UI con nuevas dimensiones notifier_->updateWindowSize(width, height); } // === Métodos privados === void UIManager::renderDebugHUD(const Engine* engine, const SceneManager* scene_manager, SimulationMode current_mode, AppMode current_app_mode, const Shape* active_shape, float shape_convergence) { int line_height = text_renderer_debug_->getTextHeight(); int margin = 8; SDL_Rect physical_viewport = getPhysicalViewport(renderer_); // --- Construir strings --- std::string appmode_text; if (current_app_mode == AppMode::LOGO) { appmode_text = "AppMode: LOGO"; } else if (current_app_mode == AppMode::DEMO) { appmode_text = "AppMode: DEMO"; } else if (current_app_mode == AppMode::DEMO_LITE) { appmode_text = "AppMode: DEMO LITE"; } else { appmode_text = "AppMode: SANDBOX"; } std::string simmode_text; if (current_mode == SimulationMode::PHYSICS) { simmode_text = "SimMode: PHYSICS"; } else if (current_mode == SimulationMode::SHAPE) { if (active_shape) { simmode_text = std::string("SimMode: SHAPE (") + active_shape->getName() + ")"; } else { simmode_text = "SimMode: SHAPE"; } } else if (current_mode == SimulationMode::BOIDS) { simmode_text = "SimMode: BOIDS"; } std::string sprite_name = engine->getCurrentTextureName(); std::transform(sprite_name.begin(), sprite_name.end(), sprite_name.begin(), ::toupper); std::string sprite_text = "Sprite: " + sprite_name; size_t ball_count = scene_manager->getBallCount(); std::string balls_text; if (ball_count >= 1000) { std::string count_str = std::to_string(ball_count); std::string formatted; int digits = static_cast(count_str.length()); for (int i = 0; i < digits; i++) { if (i > 0 && (digits - i) % 3 == 0) formatted += ','; formatted += count_str[i]; } balls_text = "Balls: " + formatted; } else { balls_text = "Balls: " + std::to_string(ball_count); } int max_auto_idx = engine->getMaxAutoScenario(); int max_auto_balls = BALL_COUNT_SCENARIOS[max_auto_idx]; if (engine->isCustomAutoAvailable() && engine->getCustomScenarioBalls() > max_auto_balls) { max_auto_balls = engine->getCustomScenarioBalls(); } std::string max_auto_text; if (max_auto_balls >= 1000) { std::string count_str = std::to_string(max_auto_balls); std::string formatted; int digits = static_cast(count_str.length()); for (int i = 0; i < digits; i++) { if (i > 0 && (digits - i) % 3 == 0) formatted += ','; formatted += count_str[i]; } max_auto_text = "Auto max: " + formatted; } else { max_auto_text = "Auto max: " + std::to_string(max_auto_balls); } std::string scaling_text; if (engine->getFullscreenEnabled() || engine->getRealFullscreenEnabled()) { ScalingMode scaling = engine->getCurrentScalingMode(); if (scaling == ScalingMode::INTEGER) { scaling_text = "Scaling: INTEGER"; } else if (scaling == ScalingMode::LETTERBOX) { scaling_text = "Scaling: LETTERBOX"; } else if (scaling == ScalingMode::STRETCH) { scaling_text = "Scaling: STRETCH"; } } else { scaling_text = "Scaling: WINDOWED"; } std::string phys_res_text = "Physical: " + std::to_string(physical_window_width_) + "x" + std::to_string(physical_window_height_); std::string logic_res_text = "Logical: " + std::to_string(engine->getCurrentScreenWidth()) + "x" + std::to_string(engine->getCurrentScreenHeight()); std::string refresh_text; int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); if (displays && num_displays > 0) { const auto* dm = SDL_GetCurrentDisplayMode(displays[0]); if (dm) { refresh_text = "Refresh: " + std::to_string(static_cast(dm->refresh_rate)) + " Hz"; } else { refresh_text = "Refresh: N/A"; } SDL_free(displays); } else { refresh_text = "Refresh: N/A"; } std::string theme_text = std::string("Theme: ") + theme_manager_->getCurrentThemeNameEN(); Uint64 ticks_ms = SDL_GetTicks(); Uint64 total_secs = ticks_ms / 1000; int hh = static_cast(total_secs / 3600); int mm = static_cast((total_secs % 3600) / 60); int ss = static_cast(total_secs % 60); char elapsed_buf[32]; SDL_snprintf(elapsed_buf, sizeof(elapsed_buf), "Elapsed: %02d:%02d:%02d", hh, mm, ss); std::string elapsed_text(elapsed_buf); // --- Construir vector de líneas en orden --- std::vector lines; lines.push_back(fps_text_); lines.push_back(appmode_text); lines.push_back(simmode_text); lines.push_back(sprite_text); lines.push_back(balls_text); lines.push_back(max_auto_text); lines.push_back(vsync_text_); lines.push_back(scaling_text); lines.push_back(phys_res_text); lines.push_back(logic_res_text); lines.push_back(refresh_text); lines.push_back(theme_text); std::string postfx_text; if (!engine->isPostFXEnabled()) { postfx_text = "PostFX: OFF"; } else { static constexpr const char* preset_names[4] = { "Vinyeta", "Scanlines", "Cromatica", "Complet" }; int mode = engine->getPostFXMode(); char buf[64]; SDL_snprintf(buf, sizeof(buf), "PostFX: %s [V:%.2f C:%.2f S:%.2f]", preset_names[mode], engine->getPostFXVignette(), engine->getPostFXChroma(), engine->getPostFXScanline()); postfx_text = buf; } lines.push_back(postfx_text); lines.push_back(elapsed_text); const Ball* first_ball = scene_manager->getFirstBall(); if (first_ball != nullptr) { lines.push_back("VelX: " + std::to_string(static_cast(first_ball->getVelocityX()))); lines.push_back("VelY: " + std::to_string(static_cast(first_ball->getVelocityY()))); SDL_FRect pos = first_ball->getPosition(); lines.push_back("Pos: (" + std::to_string(static_cast(pos.x)) + ", " + std::to_string(static_cast(pos.y)) + ")"); lines.push_back("Gravity: " + std::to_string(static_cast(first_ball->getGravityForce()))); lines.push_back(first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO"); lines.push_back("Loss: " + std::to_string(first_ball->getLossCoefficient()).substr(0, 4)); lines.push_back("Dir: " + gravityDirectionToString(static_cast(scene_manager->getCurrentGravity()))); } if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) { int convergence_percent = static_cast(shape_convergence * 100.0f); lines.push_back("Convergence: " + std::to_string(convergence_percent) + "%"); } // --- Render con desbordamiento a segunda columna --- int max_lines = (physical_viewport.h - 2 * margin) / line_height; if (max_lines < 1) max_lines = 1; int col_width = physical_viewport.w / 2; for (int i = 0; i < static_cast(lines.size()); i++) { int col = i / max_lines; int row = i % max_lines; int x = margin + col * col_width; int y = margin + row * line_height; text_renderer_debug_->printAbsoluteShadowed(x, y, lines[i].c_str()); } } std::string UIManager::gravityDirectionToString(int direction) const { switch (direction) { case 0: return "Abajo"; // DOWN case 1: return "Arriba"; // UP case 2: return "Izquierda"; // LEFT case 3: return "Derecha"; // RIGHT default: return "Desconocida"; } } int UIManager::calculateFontSize(int logical_height) const { // Escalado híbrido basado en ALTURA LÓGICA (resolución interna, sin zoom) // Esto asegura que el tamaño de fuente sea consistente independientemente del zoom de ventana // - Proporcional en extremos (muy bajo/alto) // - Escalonado en rango medio (estabilidad) int font_size = 14; // Default fallback if (logical_height < 300) { // Rango bajo: proporcional (240px→9.6, 280px→11.2) font_size = logical_height / 25; } else if (logical_height < 380) { // Rango muy bajo (300-379px) → 10px (crítico para 640x360) font_size = 10; } else if (logical_height < 500) { // Rango medio-bajo (380-499px) → 12px font_size = 12; } else if (logical_height < 700) { // Rango medio (500-699px) → 14px font_size = 14; } else if (logical_height < 900) { // Rango medio-alto (700-899px) → 18px font_size = 18; } else if (logical_height < 1200) { // Rango alto (900-1199px): 900→22, 1080→27, 1199→29 font_size = logical_height / 40; } else if (logical_height < 1600) { // Rango muy alto (1200-1599px): 1200→25, 1440→30 font_size = logical_height / 48; } else { // Rango ultra (>=1600px): 1600→26, 2000→33, 2160→36 font_size = logical_height / 60; } // Aplicar límites: mínimo 9px, máximo 72px if (font_size < 9) font_size = 9; if (font_size > 72) font_size = 72; return font_size; }