diff --git a/source/engine.hpp b/source/engine.hpp index 1f8a81d..02ade5d 100644 --- a/source/engine.hpp +++ b/source/engine.hpp @@ -108,6 +108,10 @@ class Engine { ScalingMode getCurrentScalingMode() const { return current_scaling_mode_; } int getCurrentScreenWidth() const { return current_screen_width_; } int getCurrentScreenHeight() const { return current_screen_height_; } + std::string getCurrentTextureName() const { + if (texture_names_.empty()) return ""; + return texture_names_[current_texture_index_]; + } int getBaseScreenWidth() const { return base_screen_width_; } int getBaseScreenHeight() const { return base_screen_height_; } int getMaxAutoScenario() const { return max_auto_scenario_; } diff --git a/source/text/textrenderer.cpp b/source/text/textrenderer.cpp index a570dae..e8a2200 100644 --- a/source/text/textrenderer.cpp +++ b/source/text/textrenderer.cpp @@ -313,6 +313,17 @@ void TextRenderer::printAbsolute(int physical_x, int physical_y, const std::stri printAbsolute(physical_x, physical_y, text.c_str(), color); } +void TextRenderer::printAbsoluteShadowed(int physical_x, int physical_y, const char* text) { + // Sombra: negro semitransparente desplazado 1px + printAbsolute(physical_x + 1, physical_y + 1, text, {0, 0, 0, 180}); + // Texto: blanco opaco + printAbsolute(physical_x, physical_y, text, {255, 255, 255, 255}); +} + +void TextRenderer::printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text) { + printAbsoluteShadowed(physical_x, physical_y, text.c_str()); +} + int TextRenderer::getTextWidth(const char* text) { if (!isInitialized() || text == nullptr) { return 0; diff --git a/source/text/textrenderer.hpp b/source/text/textrenderer.hpp index e9fe481..eea7b05 100644 --- a/source/text/textrenderer.hpp +++ b/source/text/textrenderer.hpp @@ -31,6 +31,10 @@ public: void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color); void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color); + // Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo + void printAbsoluteShadowed(int physical_x, int physical_y, const char* text); + void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text); + // Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad) int getTextWidth(const char* text); diff --git a/source/ui/ui_manager.cpp b/source/ui/ui_manager.cpp index 3668754..bcb4a95 100644 --- a/source/ui/ui_manager.cpp +++ b/source/ui/ui_manager.cpp @@ -1,6 +1,7 @@ #include "ui_manager.hpp" #include +#include #include #include "ball.hpp" // for Ball @@ -84,7 +85,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, text_renderer_notifier_ = new TextRenderer(); // Inicializar renderers con tamaño dinámico - text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true); + text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", std::max(9, current_font_size_ - 2), true); text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true); // Crear y configurar sistema de notificaciones @@ -109,7 +110,7 @@ void UIManager::update(Uint64 current_time, float delta_time) { fps_current_ = fps_frame_count_; fps_frame_count_ = 0; fps_last_time_ = current_time; - fps_text_ = "fps: " + std::to_string(fps_current_); + fps_text_ = "FPS: " + std::to_string(fps_current_); } // Actualizar sistema de notificaciones @@ -180,7 +181,7 @@ void UIManager::updatePhysicalWindowSize(int width, int height) { // Reinicializar text renderers con nuevo tamaño if (text_renderer_debug_) { - text_renderer_debug_->reinitialize(current_font_size_); + text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2)); } if (text_renderer_notifier_) { text_renderer_notifier_->reinitialize(current_font_size_); @@ -204,42 +205,23 @@ void UIManager::renderDebugHUD(const Engine* engine, 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 - - // Obtener viewport FÍSICO (píxeles reales, no lógicos) - // CRÍTICO: En F3, SDL_GetRenderViewport() devuelve coordenadas LÓGICAS, - // pero printAbsolute() trabaja en píxeles FÍSICOS. Usar helper para obtener - // viewport en coordenadas físicas. + int margin = 8; SDL_Rect physical_viewport = getPhysicalViewport(renderer_); - // =========================== - // COLUMNA LEFT (Sistema) - // =========================== - int left_y = margin; + // --- Construir strings --- - // AppMode (antes estaba centrado, ahora va a la izquierda) std::string appmode_text; - SDL_Color appmode_color = {255, 255, 255, 255}; // Blanco por defecto - if (current_app_mode == AppMode::LOGO) { appmode_text = "AppMode: LOGO"; - appmode_color = {255, 128, 0, 255}; // Naranja } else if (current_app_mode == AppMode::DEMO) { appmode_text = "AppMode: DEMO"; - appmode_color = {255, 165, 0, 255}; // Naranja } else if (current_app_mode == AppMode::DEMO_LITE) { appmode_text = "AppMode: DEMO LITE"; - appmode_color = {255, 200, 0, 255}; // Amarillo-naranja } else { appmode_text = "AppMode: SANDBOX"; - appmode_color = {0, 255, 128, 255}; // Verde claro } - text_renderer_debug_->printAbsolute(margin, left_y, appmode_text.c_str(), appmode_color); - left_y += line_height; - // SimulationMode std::string simmode_text; if (current_mode == SimulationMode::PHYSICS) { simmode_text = "SimMode: PHYSICS"; @@ -252,40 +234,36 @@ void UIManager::renderDebugHUD(const Engine* engine, } else if (current_mode == SimulationMode::BOIDS) { simmode_text = "SimMode: BOIDS"; } - text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian - left_y += line_height; - // Número de pelotas (escenario actual) + 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) { - // Formatear con separador de miles (ejemplo: 5,000 o 50,000) std::string count_str = std::to_string(ball_count); std::string formatted; - int digits = count_str.length(); + int digits = static_cast(count_str.length()); for (int i = 0; i < digits; i++) { - if (i > 0 && (digits - i) % 3 == 0) { - formatted += ','; - } + 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); } - text_renderer_debug_->printAbsolute(margin, left_y, balls_text.c_str(), {128, 255, 128, 255}); // Verde claro - left_y += line_height; - // Máximo de pelotas en modos automáticos (resultado del benchmark) int max_auto_idx = engine->getMaxAutoScenario(); int max_auto_balls = BALL_COUNT_SCENARIOS[max_auto_idx]; - if (engine->isCustomAutoAvailable() && engine->getCustomScenarioBalls() > max_auto_balls) + 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 = count_str.length(); + 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]; @@ -294,14 +272,7 @@ void UIManager::renderDebugHUD(const Engine* engine, } else { max_auto_text = "Auto max: " + std::to_string(max_auto_balls); } - text_renderer_debug_->printAbsolute(margin, left_y, max_auto_text.c_str(), {128, 255, 128, 255}); // Verde claro - left_y += line_height; - // V-Sync - text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian - left_y += line_height; - - // Modo de escalado (INTEGER/LETTERBOX/STRETCH o WINDOWED si no está en fullscreen) std::string scaling_text; if (engine->getFullscreenEnabled() || engine->getRealFullscreenEnabled()) { ScalingMode scaling = engine->getCurrentScalingMode(); @@ -315,20 +286,10 @@ void UIManager::renderDebugHUD(const Engine* engine, } else { scaling_text = "Scaling: WINDOWED"; } - text_renderer_debug_->printAbsolute(margin, left_y, scaling_text.c_str(), {255, 255, 0, 255}); // Amarillo - left_y += line_height; - // Resolución física (píxeles reales de la ventana) std::string phys_res_text = "Physical: " + std::to_string(physical_window_width_) + "x" + std::to_string(physical_window_height_); - text_renderer_debug_->printAbsolute(margin, left_y, phys_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro - left_y += line_height; - - // Resolución lógica (resolución interna del renderizador) std::string logic_res_text = "Logical: " + std::to_string(engine->getCurrentScreenWidth()) + "x" + std::to_string(engine->getCurrentScreenHeight()); - text_renderer_debug_->printAbsolute(margin, left_y, logic_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro - left_y += line_height; - // Display refresh rate (obtener de SDL) std::string refresh_text; int num_displays = 0; SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); @@ -343,83 +304,62 @@ void UIManager::renderDebugHUD(const Engine* engine, } else { refresh_text = "Refresh: N/A"; } - text_renderer_debug_->printAbsolute(margin, left_y, refresh_text.c_str(), {255, 255, 128, 255}); // Amarillo claro - left_y += line_height; - // Tema actual (delegado a ThemeManager) std::string theme_text = std::string("Theme: ") + theme_manager_->getCurrentThemeNameEN(); - text_renderer_debug_->printAbsolute(margin, left_y, theme_text.c_str(), {128, 255, 255, 255}); // Cian claro - left_y += line_height; - // =========================== - // COLUMNA RIGHT (Primera pelota) - // =========================== - int right_y = margin; + 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); - // FPS counter (esquina superior derecha) - int fps_text_width = text_renderer_debug_->getTextWidthPhysical(fps_text_.c_str()); - int fps_x = physical_viewport.w - fps_text_width - margin; - text_renderer_debug_->printAbsolute(fps_x, right_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo - right_y += line_height; + // --- 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); + lines.push_back(elapsed_text); - // Info de la primera pelota (si existe) const Ball* first_ball = scene_manager->getFirstBall(); if (first_ball != nullptr) { - // Posición X, Y + 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(); - std::string pos_text = "Pos: (" + std::to_string(static_cast(pos.x)) + ", " + std::to_string(static_cast(pos.y)) + ")"; - int pos_width = text_renderer_debug_->getTextWidthPhysical(pos_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - pos_width - margin, right_y, pos_text.c_str(), {255, 128, 128, 255}); // Rojo claro - right_y += line_height; - - // Velocidad X - int vx_int = static_cast(first_ball->getVelocityX()); - std::string vx_text = "VelX: " + std::to_string(vx_int); - int vx_width = text_renderer_debug_->getTextWidthPhysical(vx_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - vx_width - margin, right_y, vx_text.c_str(), {128, 255, 128, 255}); // Verde claro - right_y += line_height; - - // Velocidad Y - int vy_int = static_cast(first_ball->getVelocityY()); - std::string vy_text = "VelY: " + std::to_string(vy_int); - int vy_width = text_renderer_debug_->getTextWidthPhysical(vy_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - vy_width - margin, right_y, vy_text.c_str(), {128, 255, 128, 255}); // Verde claro - right_y += line_height; - - // Fuerza de gravedad - int grav_int = static_cast(first_ball->getGravityForce()); - std::string grav_text = "Gravity: " + std::to_string(grav_int); - int grav_width = text_renderer_debug_->getTextWidthPhysical(grav_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - grav_width - margin, right_y, grav_text.c_str(), {255, 255, 128, 255}); // Amarillo claro - right_y += line_height; - - // Estado superficie - std::string surface_text = first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO"; - int surface_width = text_renderer_debug_->getTextWidthPhysical(surface_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - surface_width - margin, right_y, surface_text.c_str(), {255, 200, 128, 255}); // Naranja claro - right_y += line_height; - - // Coeficiente de rebote (loss) - float loss_val = first_ball->getLossCoefficient(); - std::string loss_text = "Loss: " + std::to_string(loss_val).substr(0, 4); - int loss_width = text_renderer_debug_->getTextWidthPhysical(loss_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - loss_width - margin, right_y, loss_text.c_str(), {255, 128, 255, 255}); // Magenta - right_y += line_height; - - // Dirección de gravedad - std::string gravity_dir_text = "Dir: " + gravityDirectionToString(static_cast(scene_manager->getCurrentGravity())); - int dir_width = text_renderer_debug_->getTextWidthPhysical(gravity_dir_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - dir_width - margin, right_y, gravity_dir_text.c_str(), {128, 255, 255, 255}); // Cian claro - right_y += line_height; + 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()))); } - // Convergencia en modo LOGO (solo cuando está activo) - Parte inferior derecha if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) { int convergence_percent = static_cast(shape_convergence * 100.0f); - std::string convergence_text = "Convergence: " + std::to_string(convergence_percent) + "%"; - int conv_width = text_renderer_debug_->getTextWidthPhysical(convergence_text.c_str()); - text_renderer_debug_->printAbsolute(physical_viewport.w - conv_width - margin, right_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja - right_y += line_height; + 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()); } }