diff --git a/CMakeLists.txt b/CMakeLists.txt index b501497..a584faa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/data/fonts/BBHSansHegarty-Regular.ttf b/data/fonts/BBHSansHegarty-Regular.ttf new file mode 100644 index 0000000..f834b91 Binary files /dev/null and b/data/fonts/BBHSansHegarty-Regular.ttf differ diff --git a/data/fonts/FunnelSans-Regular.ttf b/data/fonts/FunnelSans-Regular.ttf new file mode 100644 index 0000000..3e497ba Binary files /dev/null and b/data/fonts/FunnelSans-Regular.ttf differ diff --git a/data/fonts/JetBrainsMono-Regular.ttf b/data/fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..436c982 Binary files /dev/null and b/data/fonts/JetBrainsMono-Regular.ttf differ diff --git a/data/fonts/Minecraftia-Regular.ttf b/data/fonts/Minecraftia-Regular.ttf new file mode 100644 index 0000000..7247ad7 Binary files /dev/null and b/data/fonts/Minecraftia-Regular.ttf differ diff --git a/data/fonts/PixelOperator.ttf b/data/fonts/PixelOperator.ttf new file mode 100644 index 0000000..34fe947 Binary files /dev/null and b/data/fonts/PixelOperator.ttf differ diff --git a/data/fonts/determination.ttf b/data/fonts/determination.ttf new file mode 100644 index 0000000..69e732b Binary files /dev/null and b/data/fonts/determination.ttf differ diff --git a/data/fonts/dogica.ttf b/data/fonts/dogica.ttf new file mode 100644 index 0000000..2d1ac57 Binary files /dev/null and b/data/fonts/dogica.ttf differ diff --git a/data/fonts/rainyhearts.ttf b/data/fonts/rainyhearts.ttf new file mode 100644 index 0000000..245cd40 Binary files /dev/null and b/data/fonts/rainyhearts.ttf differ diff --git a/source/engine.cpp b/source/engine.cpp index 6713b40..31cac64 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -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(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(); @@ -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(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(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(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(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(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(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(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(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(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(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(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(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(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; + text_ = "Escala " + std::to_string(static_cast(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; int text_width = static_cast(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(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; + text_ = "Escala " + std::to_string(static_cast(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; int text_width = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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; } diff --git a/source/engine.h b/source/engine.h index 831378b..6570aec 100644 --- a/source/engine.h +++ b/source/engine.h @@ -10,18 +10,19 @@ #include // for string #include // 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 batch_vertices_; std::vector 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 diff --git a/source/text/textrenderer.cpp b/source/text/textrenderer.cpp new file mode 100644 index 0000000..92c0862 --- /dev/null +++ b/source/text/textrenderer.cpp @@ -0,0 +1,112 @@ +#include "textrenderer.h" +#include +#include + +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(x); + dest_rect.y = static_cast(y); + dest_rect.w = static_cast(text_surface->w); + dest_rect.h = static_cast(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_); +} diff --git a/source/text/textrenderer.h b/source/text/textrenderer.h new file mode 100644 index 0000000..7bbb5c4 --- /dev/null +++ b/source/text/textrenderer.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +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_; +}; diff --git a/source/theme_manager.cpp b/source/theme_manager.cpp index 7d7086c..c88cf48 100644 --- a/source/theme_manager.cpp +++ b/source/theme_manager.cpp @@ -17,8 +17,8 @@ void ThemeManager::initialize() { // 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas themes_.push_back(std::make_unique( - "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( - "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( - "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( - "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( - "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( - "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( - "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( - "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( - "SUNRISE", - "AMANECER", + "Sunrise", + "Amanecer", 255, 200, 100, // Color texto: amarillo cálido std::vector{ // 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( - "OCEAN WAVES", - "OLAS OCEANICAS", + "Ocean Waves", + "Olas Oceánicas", 100, 220, 255, // Color texto: cian claro std::vector{ // 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( - "NEON PULSE", - "PULSO NEON", + "Neon Pulse", + "Pulso Neón", 255, 60, 255, // Color texto: magenta brillante std::vector{ // 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( - "FIRE", - "FUEGO", + "Fire", + "Fuego", 255, 150, 80, // Color texto: naranja cálido std::vector{ // 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( - "AURORA", - "AURORA", + "Aurora", + "Aurora", 150, 255, 200, // Color texto: verde claro std::vector{ // 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( - "VOLCANIC", - "VOLCAN", + "Volcanic", + "Volcán", 200, 120, 80, // Color texto: naranja apagado std::vector{ // Keyframe 0: Ceniza oscura (pre-erupción)