Migración de dbgtxt a SDL_TTF + conversión de textos a mixed case

Sistema de texto:
- Reemplazado dbgtxt.cpp (bitmap 8x8) por TextRenderer (SDL_TTF)
- Creado source/text/ con TextRenderer class
- Añadidas fuentes TrueType en data/fonts/
- Implementados dos TextRenderer (display + debug) con escalado dinámico
- Constantes configurables: TEXT_FONT_PATH, TEXT_BASE_SIZE, TEXT_ANTIALIASING

Correcciones de centrado:
- Reemplazado text.length() * 8 por text_renderer_.getTextWidth() en ~25 lugares
- Texto de tecla F ahora se centra correctamente
- Texto de modo (Demo/Logo/Lite) fijo en tercera fila del HUD debug
- Implementado espaciado dinámico con getTextHeight()

Conversión a mixed case:
- ~26 textos de display cambiados de ALL CAPS a mixed case
- 15 nombres de temas en theme_manager.cpp convertidos a mixed case
- Ejemplos: "FPS" → "fps", "MODO FISICA" → "Modo Física", "DEMO MODE ON" → "Modo Demo: On"
- Temas: "SUNSET" → "Sunset", "OCEANO" → "Océano", etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-09 20:18:01 +02:00
parent c50ecbc02a
commit f00b08b6be
14 changed files with 338 additions and 138 deletions
+14 -6
View File
@@ -16,8 +16,16 @@ if (NOT SDL3_FOUND)
message(FATAL_ERROR "SDL3 no encontrado. Por favor, verifica su instalación.")
endif()
# Buscar SDL3_ttf
find_package(SDL3_ttf REQUIRED)
# Si no se encuentra SDL3_ttf, generar un error
if (NOT SDL3_ttf_FOUND)
message(FATAL_ERROR "SDL3_ttf no encontrado. Por favor, verifica su instalación.")
endif()
# Archivos fuente (excluir main_old.cpp)
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp source/themes/*.cpp)
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp source/themes/*.cpp source/text/*.cpp)
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
# Comprobar si se encontraron archivos fuente
@@ -28,17 +36,17 @@ endif()
# Detectar la plataforma y configuraciones específicas
if(WIN32)
set(PLATFORM windows)
set(LINK_LIBS ${SDL3_LIBRARIES} mingw32 ws2_32)
set(LINK_LIBS SDL3::SDL3 SDL3_ttf::SDL3_ttf mingw32 ws2_32)
elseif(UNIX AND NOT APPLE)
set(PLATFORM linux)
set(LINK_LIBS ${SDL3_LIBRARIES})
set(LINK_LIBS SDL3::SDL3 SDL3_ttf::SDL3_ttf)
elseif(APPLE)
set(PLATFORM macos)
set(LINK_LIBS ${SDL3_LIBRARIES})
set(LINK_LIBS SDL3::SDL3 SDL3_ttf::SDL3_ttf)
endif()
# Incluir directorios de SDL3
include_directories(${SDL3_INCLUDE_DIRS})
# Incluir directorios de SDL3 y SDL3_ttf
include_directories(${SDL3_INCLUDE_DIRS} ${SDL3_ttf_INCLUDE_DIRS})
# Añadir el ejecutable reutilizando el nombre del proyecto
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+115 -79
View File
@@ -22,7 +22,6 @@
#endif
#include "ball.h" // for Ball
#include "external/dbgtxt.h" // for dbg_init, dbg_print
#include "external/mouse.h" // for Mouse namespace
#include "external/texture.h" // for Texture
#include "shapes/atom_shape.h" // for AtomShape
@@ -203,7 +202,22 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
current_ball_size_ = texture_->getWidth(); // Obtener tamaño dinámicamente
srand(static_cast<unsigned>(time(nullptr)));
dbg_init(renderer_);
// Calcular tamaño de fuente escalado según resolución
// Usa las constantes configurables de la clase
int font_size = (base_screen_height_ * TEXT_BASE_SIZE) / 240;
// Inicializar TextRenderer para display (texto centrado)
if (!text_renderer_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING)) {
SDL_Log("Error al inicializar TextRenderer (display)");
return false;
}
// Inicializar TextRenderer para debug (HUD)
if (!text_renderer_debug_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING)) {
SDL_Log("Error al inicializar TextRenderer (debug)");
return false;
}
// Inicializar ThemeManager
theme_manager_ = std::make_unique<ThemeManager>();
@@ -270,7 +284,7 @@ void Engine::update() {
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_);
}
// Bifurcar actualización según modo activo
@@ -435,7 +449,7 @@ void Engine::handleEvents() {
// Mostrar nombre del tema (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -450,7 +464,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -464,7 +478,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -478,7 +492,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -492,7 +506,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -506,7 +520,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -519,7 +533,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(5); // MONOCHROME
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -532,7 +546,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(6); // LAVENDER
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -545,7 +559,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(7); // CRIMSON
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -558,7 +572,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(8); // EMERALD
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -571,7 +585,7 @@ void Engine::handleEvents() {
theme_manager_->switchToTheme(9); // SUNRISE
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -585,8 +599,8 @@ void Engine::handleEvents() {
// Mostrar feedback visual (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) {
text_ = (theme_page_ == 0) ? "PAGINA 1" : "PAGINA 2";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = (theme_page_ == 0) ? "Página 1" : "Página 2";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -602,7 +616,7 @@ void Engine::handleEvents() {
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ += SHAPE_SCALE_STEP;
clampShapeScale();
text_ = "SCALE " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
@@ -614,7 +628,7 @@ void Engine::handleEvents() {
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ -= SHAPE_SCALE_STEP;
clampShapeScale();
text_ = "SCALE " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
@@ -625,7 +639,7 @@ void Engine::handleEvents() {
case SDLK_KP_MULTIPLY:
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
text_ = "SCALE RESET (100%)";
text_ = "Escala reiniciada (100%)";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
@@ -636,7 +650,7 @@ void Engine::handleEvents() {
case SDLK_KP_DIVIDE:
if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
text_ = depth_zoom_enabled_ ? "DEPTH ZOOM ON" : "DEPTH ZOOM OFF";
text_ = depth_zoom_enabled_ ? "Zoom profundidad: On" : "Zoom profundidad: Off";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
@@ -722,16 +736,16 @@ void Engine::handleEvents() {
if (current_app_mode_ == AppMode::DEMO) {
// Desactivar DEMO → MANUAL
setState(AppMode::MANUAL);
text_ = "DEMO MODE OFF";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = "Modo Demo: Off";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
} else {
// Activar DEMO (desde cualquier otro modo)
setState(AppMode::DEMO);
randomizeOnDemoStart(false);
text_ = "DEMO MODE ON";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = "Modo Demo: On";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -743,16 +757,16 @@ void Engine::handleEvents() {
if (current_app_mode_ == AppMode::DEMO_LITE) {
// Desactivar DEMO_LITE → MANUAL
setState(AppMode::MANUAL);
text_ = "DEMO LITE OFF";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = "Modo Demo Lite: Off";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
} else {
// Activar DEMO_LITE (desde cualquier otro modo)
setState(AppMode::DEMO_LITE);
randomizeOnDemoStart(true);
text_ = "DEMO LITE ON";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = "Modo Demo Lite: On";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -763,11 +777,11 @@ void Engine::handleEvents() {
toggleLogoMode();
// Mostrar texto informativo
if (current_app_mode_ == AppMode::LOGO) {
text_ = "LOGO MODE ON";
text_ = "Modo Logo: On";
} else {
text_ = "LOGO MODE OFF";
text_ = "Modo Logo: Off";
}
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
break;
@@ -875,91 +889,115 @@ void Engine::render() {
theme_manager_->getCurrentThemeTextColor(text_color_r, text_color_g, text_color_b);
const char* theme_name_es = theme_manager_->getCurrentThemeNameES();
// Calcular espaciado dinámico
int line_height = text_renderer_.getTextHeight();
int margin = 8;
// Texto del número de pelotas con color del tema
dbg_print(text_pos_, 8, text_.c_str(), text_color_r, text_color_g, text_color_b);
text_renderer_.print(text_pos_, margin, text_.c_str(), text_color_r, text_color_g, text_color_b);
// Mostrar nombre del tema en castellano debajo del número de pelotas
// (solo si text_ NO es ya el nombre del tema, para evitar duplicación)
if (theme_name_es != nullptr && text_ != theme_name_es) {
int theme_text_width = static_cast<int>(strlen(theme_name_es) * 8); // 8 píxeles por carácter
int theme_x = (current_screen_width_ - theme_text_width) / 2; // Centrar horizontalmente
int theme_text_width = text_renderer_.getTextWidth(theme_name_es);
int theme_x = (current_screen_width_ - theme_text_width) / 2; // Centrar horizontalmente
int theme_y = margin + line_height; // Espaciado dinámico
// Texto del nombre del tema con el mismo color
dbg_print(theme_x, 24, theme_name_es, text_color_r, text_color_g, text_color_b);
text_renderer_.print(theme_x, theme_y, theme_name_es, text_color_r, text_color_g, text_color_b);
}
}
// Debug display (solo si está activado con tecla H)
if (show_debug_) {
// Obtener altura de línea para espaciado dinámico (usando fuente debug)
int line_height = text_renderer_debug_.getTextHeight();
int margin = 8; // Margen constante
int current_y = margin; // Y inicial
// Mostrar contador de FPS en esquina superior derecha
int fps_text_width = static_cast<int>(fps_text_.length() * 8); // 8 píxeles por carácter
int fps_x = current_screen_width_ - fps_text_width - 8; // 8 píxeles de margen
dbg_print(fps_x, 8, fps_text_.c_str(), 255, 255, 0); // Amarillo para distinguir
int fps_text_width = text_renderer_debug_.getTextWidth(fps_text_.c_str());
int fps_x = current_screen_width_ - fps_text_width - margin;
text_renderer_debug_.print(fps_x, current_y, fps_text_.c_str(), 255, 255, 0); // Amarillo
// Mostrar estado V-Sync en esquina superior izquierda
dbg_print(8, 8, vsync_text_.c_str(), 0, 255, 255); // Cian para distinguir
text_renderer_debug_.print(margin, current_y, vsync_text_.c_str(), 0, 255, 255); // Cian
current_y += line_height;
// Debug: Mostrar valores de la primera pelota (si existe)
if (!balls_.empty()) {
// Línea 1: Gravedad (solo números enteros)
// Línea 1: Gravedad
int grav_int = static_cast<int>(balls_[0]->getGravityForce());
std::string grav_text = "GRAV " + std::to_string(grav_int);
dbg_print(8, 24, grav_text.c_str(), 255, 0, 255); // Magenta para debug
std::string grav_text = "Gravedad: " + std::to_string(grav_int);
text_renderer_debug_.print(margin, current_y, grav_text.c_str(), 255, 0, 255); // Magenta
current_y += line_height;
// Línea 2: Velocidad Y (solo números enteros)
// Línea 2: Velocidad Y
int vy_int = static_cast<int>(balls_[0]->getVelocityY());
std::string vy_text = "VY " + std::to_string(vy_int);
dbg_print(8, 32, vy_text.c_str(), 255, 0, 255); // Magenta para debug
std::string vy_text = "Velocidad Y: " + std::to_string(vy_int);
text_renderer_debug_.print(margin, current_y, vy_text.c_str(), 255, 0, 255); // Magenta
current_y += line_height;
// Línea 3: Estado superficie
std::string surface_text = balls_[0]->isOnSurface() ? "SURFACE YES" : "SURFACE NO";
dbg_print(8, 40, surface_text.c_str(), 255, 0, 255); // Magenta para debug
std::string surface_text = balls_[0]->isOnSurface() ? "Superficie: Sí" : "Superficie: No";
text_renderer_debug_.print(margin, current_y, surface_text.c_str(), 255, 0, 255); // Magenta
current_y += line_height;
// Línea 4: Coeficiente de rebote (loss)
float loss_val = balls_[0]->getLossCoefficient();
std::string loss_text = "LOSS " + std::to_string(loss_val).substr(0, 4); // Solo 2 decimales
dbg_print(8, 48, loss_text.c_str(), 255, 0, 255); // Magenta para debug
std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4);
text_renderer_debug_.print(margin, current_y, loss_text.c_str(), 255, 0, 255); // Magenta
current_y += line_height;
// Línea 5: Dirección de gravedad
std::string gravity_dir_text = "GRAVITY " + gravityDirectionToString(current_gravity_);
dbg_print(8, 56, gravity_dir_text.c_str(), 255, 255, 0); // Amarillo para dirección
std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(current_gravity_);
text_renderer_debug_.print(margin, current_y, gravity_dir_text.c_str(), 255, 255, 0); // Amarillo
current_y += line_height;
}
// Debug: Mostrar tema actual (delegado a ThemeManager)
std::string theme_text = std::string("THEME ") + theme_manager_->getCurrentThemeNameEN();
dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN();
text_renderer_debug_.print(margin, current_y, theme_text.c_str(), 255, 255, 128); // Amarillo claro
current_y += line_height;
// Debug: Mostrar modo de simulación actual
std::string mode_text;
if (current_mode_ == SimulationMode::PHYSICS) {
mode_text = "MODE PHYSICS";
mode_text = "Modo: Física";
} else if (active_shape_) {
mode_text = std::string("MODE ") + active_shape_->getName();
mode_text = std::string("Modo: ") + active_shape_->getName();
} else {
mode_text = "MODE SHAPE";
mode_text = "Modo: Forma";
}
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
text_renderer_debug_.print(margin, current_y, mode_text.c_str(), 0, 255, 128); // 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<int>(shape_convergence_ * 100.0f);
std::string convergence_text = "CONV " + std::to_string(convergence_percent);
dbg_print(8, 80, convergence_text.c_str(), 255, 128, 0); // Naranja para convergencia
std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%";
text_renderer_debug_.print(margin, current_y, convergence_text.c_str(), 255, 128, 0); // 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) {
int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles
const char* logo_text = "Modo Logo";
int logo_text_width = text_renderer_debug_.getTextWidth(logo_text);
int logo_x = (current_screen_width_ - logo_text_width) / 2;
dbg_print(logo_x, 80, "LOGO MODE", 255, 128, 0); // Naranja para Logo Mode
text_renderer_debug_.print(logo_x, fixed_y, logo_text, 255, 128, 0); // Naranja
} else if (current_app_mode_ == AppMode::DEMO) {
int demo_text_width = 9 * 8; // "DEMO MODE" = 9 caracteres × 8 píxeles
const char* demo_text = "Modo Demo";
int demo_text_width = text_renderer_debug_.getTextWidth(demo_text);
int demo_x = (current_screen_width_ - demo_text_width) / 2;
dbg_print(demo_x, 80, "DEMO MODE", 255, 165, 0); // Naranja para DEMO
text_renderer_debug_.print(demo_x, fixed_y, demo_text, 255, 165, 0); // Naranja
} else if (current_app_mode_ == AppMode::DEMO_LITE) {
int lite_text_width = 14 * 8; // "DEMO LITE MODE" = 14 caracteres × 8 píxeles
const char* lite_text = "Modo Demo Lite";
int lite_text_width = text_renderer_debug_.getTextWidth(lite_text);
int lite_x = (current_screen_width_ - lite_text_width) / 2;
dbg_print(lite_x, 80, "DEMO LITE MODE", 255, 200, 0); // Amarillo-naranja para DEMO LITE
text_renderer_debug_.print(lite_x, fixed_y, lite_text, 255, 200, 0); // Amarillo-naranja
}
}
@@ -1006,11 +1044,11 @@ void Engine::setText() {
int num_balls = BALL_COUNT_SCENARIOS[scenario_];
if (num_balls == 1) {
text_ = "1 PELOTA";
text_ = "1 pelota";
} else {
text_ = std::to_string(num_balls) + " PELOTAS";
text_ = std::to_string(num_balls) + " pelotas";
}
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; // Centrar texto
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2; // Centrar texto
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -1093,7 +1131,7 @@ void Engine::changeGravityDirection(GravityDirection direction) {
void Engine::toggleVSync() {
vsync_enabled_ = !vsync_enabled_;
vsync_text_ = vsync_enabled_ ? "VSYNC ON" : "VSYNC OFF";
vsync_text_ = vsync_enabled_ ? "V-Sync: On" : "V-Sync: Off";
// Aplicar el cambio de V-Sync al renderizador
SDL_SetRenderVSync(renderer_, vsync_enabled_ ? 1 : 0);
@@ -1199,9 +1237,9 @@ void Engine::toggleIntegerScaling() {
SDL_SetRenderLogicalPresentation(renderer_, current_screen_width_, current_screen_height_, presentation);
// Mostrar texto informativo
text_ = "SCALING: ";
text_ = "Escalado: ";
text_ += mode_name;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -2021,8 +2059,8 @@ void Engine::switchTexture() {
std::string texture_name = texture_names_[current_texture_index_];
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
text_ = "SPRITE: " + texture_name;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
text_ = "Sprite: " + texture_name;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
@@ -2067,9 +2105,8 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
// Mostrar texto informativo (solo si NO estamos en modo demo o logo)
if (current_app_mode_ == AppMode::MANUAL) {
text_ = "MODO FISICA";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_ = "Modo Física";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
}
@@ -2132,9 +2169,8 @@ void Engine::activateShape(ShapeType type) {
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo)
if (active_shape_ && current_app_mode_ == AppMode::MANUAL) {
text_ = std::string("MODO ") + active_shape_->getName();
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_ = std::string("Modo ") + active_shape_->getName();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
}
+33 -25
View File
@@ -10,18 +10,19 @@
#include <string> // for string
#include <vector> // for vector
#include "ball.h" // for Ball
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.h" // for Texture
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
#include "theme_manager.h" // for ThemeManager
#include "ball.h" // for Ball
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.h" // for Texture
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
#include "text/textrenderer.h" // for TextRenderer
#include "theme_manager.h" // for ThemeManager
// Modos de aplicación mutuamente excluyentes
enum class AppMode {
MANUAL, // Control manual del usuario
DEMO, // Modo demo completo (auto-play)
DEMO_LITE, // Modo demo lite (solo física/figuras)
LOGO // Modo logo (easter egg)
MANUAL, // Control manual del usuario
DEMO, // Modo demo completo (auto-play)
DEMO_LITE, // Modo demo lite (solo física/figuras)
LOGO // Modo logo (easter egg)
};
class Engine {
@@ -54,6 +55,8 @@ class Engine {
// UI y debug
bool show_debug_ = false;
bool show_text_ = true;
TextRenderer text_renderer_; // Sistema de renderizado de texto para display (centrado)
TextRenderer text_renderer_debug_; // Sistema de renderizado de texto para debug (HUD)
// Sistema de zoom dinámico
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
@@ -93,22 +96,22 @@ class Engine {
bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado
// Sistema de Modo DEMO (auto-play)
AppMode current_app_mode_ = AppMode::MANUAL; // Modo actual (mutuamente excluyente)
AppMode previous_app_mode_ = AppMode::MANUAL; // Modo previo antes de entrar a LOGO
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
AppMode current_app_mode_ = AppMode::MANUAL; // Modo actual (mutuamente excluyente)
AppMode previous_app_mode_ = AppMode::MANUAL; // Modo previo antes de entrar a LOGO
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
// Sistema de convergencia para LOGO MODE (escala con resolución)
float shape_convergence_ = 0.0f; // % de pelotas cerca del objetivo (0.0-1.0)
float logo_convergence_threshold_ = 0.90f; // Threshold aleatorio (75-100%)
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup)
float shape_convergence_ = 0.0f; // % de pelotas cerca del objetivo (0.0-1.0)
float logo_convergence_threshold_ = 0.90f; // Threshold aleatorio (75-100%)
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup)
// Sistema de espera de flips en LOGO MODE (camino alternativo)
bool logo_waiting_for_flip_ = false; // true si eligió el camino "esperar flip"
int logo_target_flip_number_ = 0; // En qué flip actuar (1, 2 o 3)
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
bool logo_waiting_for_flip_ = false; // true si eligió el camino "esperar flip"
int logo_target_flip_number_ = 0; // En qué flip actuar (1, 2 o 3)
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
int logo_previous_theme_ = 0; // Índice de tema (0-9)
@@ -119,6 +122,11 @@ class Engine {
std::vector<SDL_Vertex> batch_vertices_;
std::vector<int> batch_indices_;
// Configuración del sistema de texto (constantes configurables)
static constexpr const char* TEXT_FONT_PATH = "data/fonts/determination.ttf";
static constexpr int TEXT_BASE_SIZE = 8; // Tamaño base para 240p
static constexpr bool TEXT_ANTIALIASING = true; // true = suavizado, false = píxeles nítidos
// Métodos principales del loop
void calculateDeltaTime();
void update();
@@ -141,7 +149,7 @@ class Engine {
std::string gravityDirectionToString(GravityDirection direction) const;
// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO)
void setState(AppMode new_mode); // Cambiar modo de aplicación (mutuamente excluyente)
void setState(AppMode new_mode); // Cambiar modo de aplicación (mutuamente excluyente)
// Sistema de Modo DEMO
void updateDemoMode();
@@ -150,9 +158,9 @@ class Engine {
void toggleGravityOnOff();
// Sistema de Modo Logo (easter egg)
void toggleLogoMode(); // Activar/desactivar modo logo manual (tecla K)
void enterLogoMode(bool from_demo = false); // Entrar al modo logo (manual o automático)
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
void toggleLogoMode(); // Activar/desactivar modo logo manual (tecla K)
void enterLogoMode(bool from_demo = false); // Entrar al modo logo (manual o automático)
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
// Sistema de cambio de sprites dinámico
void switchTexture(); // Cambia a siguiente textura disponible
+112
View File
@@ -0,0 +1,112 @@
#include "textrenderer.h"
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true) {
}
TextRenderer::~TextRenderer() {
cleanup();
}
bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing) {
renderer_ = renderer;
font_size_ = font_size;
use_antialiasing_ = use_antialiasing;
// Inicializar SDL_ttf si no está inicializado
if (!TTF_WasInit()) {
if (!TTF_Init()) {
SDL_Log("Error al inicializar SDL_ttf: %s", SDL_GetError());
return false;
}
}
// Cargar la fuente
font_ = TTF_OpenFont(font_path, font_size);
if (font_ == nullptr) {
SDL_Log("Error al cargar fuente '%s': %s", font_path, SDL_GetError());
return false;
}
return true;
}
void TextRenderer::cleanup() {
if (font_ != nullptr) {
TTF_CloseFont(font_);
font_ = nullptr;
}
renderer_ = nullptr;
}
void TextRenderer::print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b) {
if (!isInitialized() || text == nullptr || text[0] == '\0') {
return;
}
// Crear superficie con el texto renderizado
SDL_Color color = {r, g, b, 255};
SDL_Surface* text_surface = nullptr;
if (use_antialiasing_) {
// Con antialiasing (suave, mejor calidad)
text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color);
} else {
// Sin antialiasing (píxeles nítidos, estilo retro)
text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color);
}
if (text_surface == nullptr) {
SDL_Log("Error al renderizar texto: %s", SDL_GetError());
return;
}
// Crear textura desde la superficie
SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface);
if (text_texture == nullptr) {
SDL_Log("Error al crear textura: %s", SDL_GetError());
SDL_DestroySurface(text_surface);
return;
}
// Preparar rectángulo de destino
SDL_FRect dest_rect;
dest_rect.x = static_cast<float>(x);
dest_rect.y = static_cast<float>(y);
dest_rect.w = static_cast<float>(text_surface->w);
dest_rect.h = static_cast<float>(text_surface->h);
// Renderizar la textura
SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect);
// Limpiar recursos
SDL_DestroyTexture(text_texture);
SDL_DestroySurface(text_surface);
}
void TextRenderer::print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b) {
print(x, y, text.c_str(), r, g, b);
}
int TextRenderer::getTextWidth(const char* text) {
if (!isInitialized() || text == nullptr) {
return 0;
}
int width = 0;
int height = 0;
if (!TTF_GetStringSize(font_, text, strlen(text), &width, &height)) {
return 0;
}
return width;
}
int TextRenderer::getTextHeight() {
if (!isInitialized()) {
return 0;
}
return TTF_GetFontHeight(font_);
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <string>
class TextRenderer {
public:
TextRenderer();
~TextRenderer();
// Inicializa el renderizador de texto con una fuente
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
// Libera recursos
void cleanup();
// Renderiza texto en la posición especificada con color RGB
void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b);
void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b);
// Obtiene el ancho de un texto renderizado
int getTextWidth(const char* text);
// Obtiene la altura de la fuente
int getTextHeight();
// Verifica si está inicializado correctamente
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
private:
SDL_Renderer* renderer_;
TTF_Font* font_;
int font_size_;
bool use_antialiasing_;
};
+28 -28
View File
@@ -17,8 +17,8 @@ void ThemeManager::initialize() {
// 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas
themes_.push_back(std::make_unique<StaticTheme>(
"SUNSET",
"ATARDECER",
"Sunset",
"Atardecer",
255, 140, 60, // Color texto: naranja cálido
180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: naranja suave
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: púrpura oscuro
@@ -30,8 +30,8 @@ void ThemeManager::initialize() {
// 1: OCEAN (Océano) - Azules, turquesas, blancos
themes_.push_back(std::make_unique<StaticTheme>(
"OCEAN",
"OCEANO",
"Ocean",
"Océano",
80, 200, 255, // Color texto: azul océano
100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior: azul cielo
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: azul marino
@@ -43,8 +43,8 @@ void ThemeManager::initialize() {
// 2: NEON - Cian, magenta, verde lima, amarillo vibrante
themes_.push_back(std::make_unique<StaticTheme>(
"NEON",
"NEON",
"Neon",
"Neón",
255, 60, 255, // Color texto: magenta brillante
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: negro azulado
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
@@ -56,8 +56,8 @@ void ThemeManager::initialize() {
// 3: FOREST (Bosque) - Verdes, marrones, amarillos otoño
themes_.push_back(std::make_unique<StaticTheme>(
"FOREST",
"BOSQUE",
"Forest",
"Bosque",
100, 255, 100, // Color texto: verde natural
144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior: verde claro
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior: marrón tierra
@@ -104,8 +104,8 @@ void ThemeManager::initialize() {
// 5: MONOCHROME (Monocromo) - Fondo negro degradado, sprites blancos
themes_.push_back(std::make_unique<StaticTheme>(
"MONOCHROME",
"MONOCROMO",
"Monochrome",
"Monocromo",
200, 200, 200, // Color texto: gris claro
20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: gris muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
@@ -117,8 +117,8 @@ void ThemeManager::initialize() {
// 6: LAVENDER (Lavanda) - Degradado violeta oscuro → azul medianoche, pelotas amarillo dorado
themes_.push_back(std::make_unique<StaticTheme>(
"LAVENDER",
"LAVANDA",
"Lavender",
"Lavanda",
255, 200, 100, // Color texto: amarillo cálido
120.0f / 255.0f, 80.0f / 255.0f, 140.0f / 255.0f, // Fondo superior: violeta oscuro
25.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul medianoche
@@ -130,8 +130,8 @@ void ThemeManager::initialize() {
// 7: CRIMSON (Carmesí) - Fondo negro-rojo oscuro, pelotas rojas uniformes
themes_.push_back(std::make_unique<StaticTheme>(
"CRIMSON",
"CARMESI",
"Crimson",
"Carmesí",
255, 100, 100, // Color texto: rojo claro
40.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: rojo muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
@@ -143,8 +143,8 @@ void ThemeManager::initialize() {
// 8: EMERALD (Esmeralda) - Fondo negro-verde oscuro, pelotas verdes uniformes
themes_.push_back(std::make_unique<StaticTheme>(
"EMERALD",
"ESMERALDA",
"Emerald",
"Esmeralda",
100, 255, 100, // Color texto: verde claro
0.0f / 255.0f, 40.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: verde muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
@@ -160,8 +160,8 @@ void ThemeManager::initialize() {
// 9: SUNRISE (Amanecer) - Noche → Alba → Día (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"SUNRISE",
"AMANECER",
"Sunrise",
"Amanecer",
255, 200, 100, // Color texto: amarillo cálido
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Noche oscura (estado inicial)
@@ -201,8 +201,8 @@ void ThemeManager::initialize() {
// 10: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"OCEAN WAVES",
"OLAS OCEANICAS",
"Ocean Waves",
"Olas Oceánicas",
100, 220, 255, // Color texto: cian claro
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Profundidad oceánica (azul oscuro)
@@ -232,8 +232,8 @@ void ThemeManager::initialize() {
// 11: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong)
themes_.push_back(std::make_unique<DynamicTheme>(
"NEON PULSE",
"PULSO NEON",
"Neon Pulse",
"Pulso Neón",
255, 60, 255, // Color texto: magenta brillante
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Apagado (negro)
@@ -263,8 +263,8 @@ void ThemeManager::initialize() {
// 12: FIRE (Fuego Vivo) - Brasas → Llamas → Inferno (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"FIRE",
"FUEGO",
"Fire",
"Fuego",
255, 150, 80, // Color texto: naranja cálido
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Brasas oscuras (estado inicial)
@@ -314,8 +314,8 @@ void ThemeManager::initialize() {
// 13: AURORA (Aurora Boreal) - Verde → Violeta → Cian (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"AURORA",
"AURORA",
"Aurora",
"Aurora",
150, 255, 200, // Color texto: verde claro
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Verde aurora (estado inicial)
@@ -365,8 +365,8 @@ void ThemeManager::initialize() {
// 14: VOLCANIC (Erupción Volcánica) - Ceniza → Erupción → Lava (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"VOLCANIC",
"VOLCAN",
"Volcanic",
"Volcán",
200, 120, 80, // Color texto: naranja apagado
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Ceniza oscura (pre-erupción)