#include "ui_manager.h" #include #include #include "../ball.h" // for Ball #include "../defines.h" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode #include "../scene/scene_manager.h" // for SceneManager #include "../shapes/shape.h" // for Shape #include "../text/textrenderer.h" // for TextRenderer #include "../theme_manager.h" // for ThemeManager #include "notifier.h" // for Notifier #include "help_overlay.h" // for HelpOverlay UIManager::UIManager() : text_renderer_(nullptr) , text_renderer_debug_(nullptr) , text_renderer_notifier_(nullptr) , notifier_(nullptr) , help_overlay_(nullptr) , show_debug_(false) , show_text_(true) , text_() , text_pos_(0) , text_init_time_(0) , 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) { } UIManager::~UIManager() { // Limpieza: Los objetos creados con new deben ser eliminados delete text_renderer_; 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) { renderer_ = renderer; theme_manager_ = theme_manager; physical_window_width_ = physical_width; physical_window_height_ = physical_height; // Crear renderers de texto text_renderer_ = new TextRenderer(); text_renderer_debug_ = new TextRenderer(); text_renderer_notifier_ = new TextRenderer(); // Inicializar renderers // (el tamaño se configura dinámicamente en Engine según resolución) text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true); text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true); text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, 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); // 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 texto obsoleto (DEPRECATED) if (show_text_) { show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION); } // Actualizar sistema de notificaciones notifier_->update(current_time); } void UIManager::render(SDL_Renderer* renderer, 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 texto obsoleto centrado (DEPRECATED - mantener temporalmente) if (show_text_) { renderObsoleteText(current_screen_width); } // Renderizar debug HUD si está activo if (show_debug_) { renderDebugHUD(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) { physical_window_width_ = width; physical_window_height_ = height; notifier_->updateWindowSize(width, height); if (help_overlay_) { help_overlay_->updatePhysicalWindowSize(width, height); } } void UIManager::setTextObsolete(const std::string& text, int pos, int current_screen_width) { text_ = text; text_pos_ = pos; text_init_time_ = SDL_GetTicks(); show_text_ = true; } // === Métodos privados === void UIManager::renderDebugHUD(const SceneManager* scene_manager, SimulationMode current_mode, AppMode current_app_mode, const Shape* active_shape, float shape_convergence) { // Obtener altura de línea para espaciado dinámico int line_height = text_renderer_debug_->getTextHeight(); int margin = 8; // Margen constante en píxeles físicos int current_y = margin; // Y inicial en píxeles físicos // Mostrar contador de FPS en esquina superior derecha int fps_text_width = text_renderer_debug_->getTextWidthPhysical(fps_text_.c_str()); int fps_x = physical_window_width_ - fps_text_width - margin; text_renderer_debug_->printAbsolute(fps_x, current_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo // Mostrar estado V-Sync en esquina superior izquierda text_renderer_debug_->printAbsolute(margin, current_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian current_y += line_height; // Debug: Mostrar valores de la primera pelota (si existe) const Ball* first_ball = scene_manager->getFirstBall(); if (first_ball != nullptr) { // Línea 1: Gravedad int grav_int = static_cast(first_ball->getGravityForce()); std::string grav_text = "Gravedad: " + std::to_string(grav_int); text_renderer_debug_->printAbsolute(margin, current_y, grav_text.c_str(), {255, 0, 255, 255}); // Magenta current_y += line_height; // Línea 2: Velocidad Y int vy_int = static_cast(first_ball->getVelocityY()); std::string vy_text = "Velocidad Y: " + std::to_string(vy_int); text_renderer_debug_->printAbsolute(margin, current_y, vy_text.c_str(), {255, 0, 255, 255}); // Magenta current_y += line_height; // Línea 3: Estado superficie std::string surface_text = first_ball->isOnSurface() ? "Superficie: Sí" : "Superficie: No"; text_renderer_debug_->printAbsolute(margin, current_y, surface_text.c_str(), {255, 0, 255, 255}); // Magenta current_y += line_height; // Línea 4: Coeficiente de rebote (loss) float loss_val = first_ball->getLossCoefficient(); std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4); text_renderer_debug_->printAbsolute(margin, current_y, loss_text.c_str(), {255, 0, 255, 255}); // Magenta current_y += line_height; // Línea 5: Dirección de gravedad std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(static_cast(scene_manager->getCurrentGravity())); text_renderer_debug_->printAbsolute(margin, current_y, gravity_dir_text.c_str(), {255, 255, 0, 255}); // Amarillo current_y += line_height; } // Debug: Mostrar tema actual (delegado a ThemeManager) std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN(); text_renderer_debug_->printAbsolute(margin, current_y, theme_text.c_str(), {255, 255, 128, 255}); // Amarillo claro current_y += line_height; // Debug: Mostrar modo de simulación actual std::string mode_text; if (current_mode == SimulationMode::PHYSICS) { mode_text = "Modo: Física"; } else if (active_shape) { mode_text = std::string("Modo: ") + active_shape->getName(); } else { mode_text = "Modo: Forma"; } text_renderer_debug_->printAbsolute(margin, current_y, mode_text.c_str(), {0, 255, 128, 255}); // Verde claro current_y += line_height; // Debug: Mostrar convergencia en modo LOGO (solo cuando está activo) if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) { int convergence_percent = static_cast(shape_convergence * 100.0f); std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%"; text_renderer_debug_->printAbsolute(margin, current_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja current_y += line_height; } // Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON) // FIJO en tercera fila (no se mueve con otros elementos del HUD) int fixed_y = margin + (line_height * 2); // Tercera fila fija if (current_app_mode == AppMode::LOGO) { const char* logo_text = "Modo Logo"; int logo_text_width = text_renderer_debug_->getTextWidthPhysical(logo_text); int logo_x = (physical_window_width_ - logo_text_width) / 2; text_renderer_debug_->printAbsolute(logo_x, fixed_y, logo_text, {255, 128, 0, 255}); // Naranja } else if (current_app_mode == AppMode::DEMO) { const char* demo_text = "Modo Demo"; int demo_text_width = text_renderer_debug_->getTextWidthPhysical(demo_text); int demo_x = (physical_window_width_ - demo_text_width) / 2; text_renderer_debug_->printAbsolute(demo_x, fixed_y, demo_text, {255, 165, 0, 255}); // Naranja } else if (current_app_mode == AppMode::DEMO_LITE) { const char* lite_text = "Modo Demo Lite"; int lite_text_width = text_renderer_debug_->getTextWidthPhysical(lite_text); int lite_x = (physical_window_width_ - lite_text_width) / 2; text_renderer_debug_->printAbsolute(lite_x, fixed_y, lite_text, {255, 200, 0, 255}); // Amarillo-naranja } } void UIManager::renderObsoleteText(int current_screen_width) { // DEPRECATED: Sistema antiguo de texto centrado // Mantener por compatibilidad temporal hasta migrar todo a Notifier // Calcular escala dinámica basada en resolución física float text_scale_x = static_cast(physical_window_width_) / 426.0f; float text_scale_y = static_cast(physical_window_height_) / 240.0f; // Obtener color del tema actual (LERP interpolado) int margin = 8; Color text_color = theme_manager_->getInterpolatedColor(0); int text_color_r = text_color.r; int text_color_g = text_color.g; int text_color_b = text_color.b; // Renderizar texto centrado usando coordenadas físicas text_renderer_->printPhysical(text_pos_, margin, text_.c_str(), text_color_r, text_color_g, text_color_b, text_scale_x, text_scale_y); } 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"; } }