From 18a8812ad7f87557065828670819078250b07518 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 12 Oct 2025 07:02:22 +0200 Subject: [PATCH] =?UTF-8?q?Help=20Overlay:=20implementaci=C3=B3n=20prelimi?= =?UTF-8?q?nar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/engine.cpp | 4 + source/engine.h | 1 + source/input/input_handler.cpp | 17 ++- source/main.cpp | 14 +- source/ui/help_overlay.cpp | 237 +++++++++++++++++++++++++++++++++ source/ui/help_overlay.h | 76 +++++++++++ source/ui/ui_manager.cpp | 21 +++ 7 files changed, 362 insertions(+), 8 deletions(-) create mode 100644 source/ui/help_overlay.cpp create mode 100644 source/ui/help_overlay.h diff --git a/source/engine.cpp b/source/engine.cpp index 8a918c8..6b19132 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -381,6 +381,10 @@ void Engine::toggleDebug() { ui_manager_->toggleDebug(); } +void Engine::toggleHelp() { + ui_manager_->toggleHelp(); +} + // Figuras 3D void Engine::toggleShapeMode() { toggleShapeModeInternal(); diff --git a/source/engine.h b/source/engine.h index 637a4a7..3eaa5b9 100644 --- a/source/engine.h +++ b/source/engine.h @@ -39,6 +39,7 @@ class Engine { // Display y depuración void toggleVSync(); void toggleDebug(); + void toggleHelp(); // Figuras 3D void toggleShapeMode(); diff --git a/source/input/input_handler.cpp b/source/input/input_handler.cpp index 817b553..2230739 100644 --- a/source/input/input_handler.cpp +++ b/source/input/input_handler.cpp @@ -53,7 +53,7 @@ bool InputHandler::processEvents(Engine& engine) { break; case SDLK_H: - engine.toggleDebug(); + engine.toggleHelp(); // Toggle ayuda de teclas break; // Toggle Física ↔ Última Figura (antes era C) @@ -99,20 +99,20 @@ bool InputHandler::processEvents(Engine& engine) { break; // Toggle Modo Boids (comportamiento de enjambre) - case SDLK_J: + case SDLK_B: engine.toggleBoidsMode(); break; - // Ciclar temas de color (movido de T a B) - case SDLK_B: + // Ciclar temas de color (movido de B a C) + case SDLK_C: { // Detectar si Shift está presionado SDL_Keymod modstate = SDL_GetModState(); if (modstate & SDL_KMOD_SHIFT) { - // Shift+B: Ciclar hacia atrás (tema anterior) + // Shift+C: Ciclar hacia atrás (tema anterior) engine.cycleTheme(false); } else { - // B solo: Ciclar hacia adelante (tema siguiente) + // C solo: Ciclar hacia adelante (tema siguiente) engine.cycleTheme(true); } } @@ -263,6 +263,11 @@ bool InputHandler::processEvents(Engine& engine) { case SDLK_K: engine.toggleLogoMode(); break; + + // Toggle Debug Display (movido de H a F12) + case SDLK_F12: + engine.toggleDebug(); + break; } } } diff --git a/source/main.cpp b/source/main.cpp index 75c182b..411fba4 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -11,13 +11,15 @@ void printHelp() { std::cout << " -w, --width Ancho de resolución (default: 320)\n"; std::cout << " -h, --height Alto de resolución (default: 240)\n"; std::cout << " -z, --zoom Zoom de ventana (default: 3)\n"; - std::cout << " -f, --fullscreen Modo pantalla completa\n"; + std::cout << " -f, --fullscreen Modo pantalla completa (F3 - letterbox)\n"; + std::cout << " -F, --real-fullscreen Modo pantalla completa real (F4 - nativo)\n"; std::cout << " --help Mostrar esta ayuda\n\n"; std::cout << "Ejemplos:\n"; std::cout << " vibe3_physics # 320x240 zoom 3 (ventana 960x720)\n"; std::cout << " vibe3_physics -w 1920 -h 1080 # 1920x1080 zoom 1 (auto)\n"; std::cout << " vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (ventana 1280x960)\n"; - std::cout << " vibe3_physics -w 1920 -h 1080 -f # 1920x1080 fullscreen\n\n"; + std::cout << " vibe3_physics -f # Fullscreen letterbox (F3)\n"; + std::cout << " vibe3_physics -F # Fullscreen real (F4 - resolución nativa)\n\n"; std::cout << "Nota: Si resolución > pantalla, se usa default. Zoom se ajusta automáticamente.\n"; } @@ -26,6 +28,7 @@ int main(int argc, char* argv[]) { int height = 0; int zoom = 0; bool fullscreen = false; + bool real_fullscreen = false; // Parsear argumentos for (int i = 1; i < argc; i++) { @@ -67,6 +70,8 @@ int main(int argc, char* argv[]) { } } else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fullscreen") == 0) { fullscreen = true; + } else if (strcmp(argv[i], "-F") == 0 || strcmp(argv[i], "--real-fullscreen") == 0) { + real_fullscreen = true; } else { std::cerr << "Error: Opción desconocida '" << argv[i] << "'\n"; printHelp(); @@ -86,6 +91,11 @@ int main(int argc, char* argv[]) { return -1; } + // Si se especificó real fullscreen (F4), activar después de inicializar + if (real_fullscreen) { + engine.toggleRealFullscreen(); + } + engine.run(); engine.shutdown(); diff --git a/source/ui/help_overlay.cpp b/source/ui/help_overlay.cpp new file mode 100644 index 0000000..73bcb71 --- /dev/null +++ b/source/ui/help_overlay.cpp @@ -0,0 +1,237 @@ +#include "help_overlay.h" + +#include // for std::min + +#include "../theme_manager.h" +#include "../text/textrenderer.h" + +HelpOverlay::HelpOverlay() + : renderer_(nullptr) + , theme_mgr_(nullptr) + , text_renderer_(nullptr) + , physical_width_(0) + , physical_height_(0) + , visible_(false) + , box_size_(0) + , box_x_(0) + , box_y_(0) { + // Llenar lista de controles (organizados por categoría, equilibrado en 2 columnas) + key_bindings_ = { + // COLUMNA 1: SIMULACIÓN + {"SIMULACIÓN", ""}, + {"1-8", "Escenarios (10 a 50,000 pelotas)"}, + {"F", "Toggle Física ↔ Última Figura"}, + {"B", "Modo Boids (enjambre)"}, + {"ESPACIO", "Impulso contra gravedad"}, + {"G", "Toggle Gravedad ON/OFF"}, + {"↑↓←→", "Dirección de gravedad"}, + {"", ""}, // Separador + + // COLUMNA 1: FIGURAS 3D + {"FIGURAS 3D", ""}, + {"Q/W/E/R", "Esfera/Lissajous/Hélice/Toroide"}, + {"T/Y/U/I", "Cubo/Cilindro/Icosaedro/Átomo"}, + {"O", "Forma PNG"}, + {"Num+/-", "Escalar figura"}, + {"Num*", "Reset escala"}, + {"Num/", "Toggle profundidad"}, + {"", ""}, // Separador + + // COLUMNA 1: VISUAL + {"VISUAL", ""}, + {"C", "Tema siguiente"}, + {"Shift+C", "Tema anterior"}, + {"NumEnter", "Página de temas"}, + {"N", "Cambiar sprite"}, + {"", ""}, // Separador -> CAMBIO DE COLUMNA + + // COLUMNA 2: PANTALLA + {"PANTALLA", ""}, + {"F1/F2", "Zoom out/in (ventana)"}, + {"F3", "Fullscreen letterbox"}, + {"F4", "Fullscreen real"}, + {"F5", "Escalado (F3 activo)"}, + {"V", "Toggle V-Sync"}, + + // COLUMNA 2: MODOS + {"MODOS", ""}, + {"D", "Modo DEMO"}, + {"Shift+D", "Pausar tema dinámico"}, + {"L", "Modo DEMO LITE"}, + {"K", "Modo LOGO (easter egg)"}, + + // COLUMNA 2: DEBUG/AYUDA + {"DEBUG/AYUDA", ""}, + {"F12", "Toggle info debug"}, + {"H", "Esta ayuda"}, + {"ESC", "Salir"} + }; +} + +HelpOverlay::~HelpOverlay() { + delete text_renderer_; +} + +void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height) { + renderer_ = renderer; + theme_mgr_ = theme_mgr; + physical_width_ = physical_width; + physical_height_ = physical_height; + + // Crear renderer de texto con tamaño reducido (18px en lugar de 24px) + text_renderer_ = new TextRenderer(); + text_renderer_->init(renderer, "data/fonts/determination.ttf", 18, true); + + calculateBoxDimensions(); +} + +void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_height) { + physical_width_ = physical_width; + physical_height_ = physical_height; + calculateBoxDimensions(); +} + +void HelpOverlay::calculateBoxDimensions() { + // 90% de la dimensión más corta (cuadrado) + int min_dimension = std::min(physical_width_, physical_height_); + box_size_ = static_cast(min_dimension * 0.9f); + + // Centrar en pantalla + box_x_ = (physical_width_ - box_size_) / 2; + box_y_ = (physical_height_ - box_size_) / 2; +} + +void HelpOverlay::render(SDL_Renderer* renderer) { + if (!visible_) return; + + // CRÍTICO: Habilitar alpha blending para que la transparencia funcione + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + // Obtener color de notificación del tema actual (para el fondo) + int notif_bg_r, notif_bg_g, notif_bg_b; + theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b); + + // Renderizar fondo semitransparente usando SDL_RenderGeometry (soporta alpha real) + float alpha = 0.95f; + SDL_Vertex bg_vertices[4]; + + // Convertir RGB a float [0.0, 1.0] + float r = notif_bg_r / 255.0f; + float g = notif_bg_g / 255.0f; + float b = notif_bg_b / 255.0f; + + // Vértice superior izquierdo + bg_vertices[0].position = {static_cast(box_x_), static_cast(box_y_)}; + bg_vertices[0].tex_coord = {0.0f, 0.0f}; + bg_vertices[0].color = {r, g, b, alpha}; + + // Vértice superior derecho + bg_vertices[1].position = {static_cast(box_x_ + box_size_), static_cast(box_y_)}; + bg_vertices[1].tex_coord = {1.0f, 0.0f}; + bg_vertices[1].color = {r, g, b, alpha}; + + // Vértice inferior derecho + bg_vertices[2].position = {static_cast(box_x_ + box_size_), static_cast(box_y_ + box_size_)}; + bg_vertices[2].tex_coord = {1.0f, 1.0f}; + bg_vertices[2].color = {r, g, b, alpha}; + + // Vértice inferior izquierdo + bg_vertices[3].position = {static_cast(box_x_), static_cast(box_y_ + box_size_)}; + bg_vertices[3].tex_coord = {0.0f, 1.0f}; + bg_vertices[3].color = {r, g, b, alpha}; + + // Índices para 2 triángulos + int bg_indices[6] = {0, 1, 2, 2, 3, 0}; + + // Renderizar sin textura (nullptr) con alpha blending + SDL_RenderGeometry(renderer, nullptr, bg_vertices, 4, bg_indices, 6); + + // Renderizar texto de ayuda + renderHelpText(); +} + +void HelpOverlay::renderHelpText() { + // Obtener 2 colores del tema para diferenciación visual + int text_r, text_g, text_b; + theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b); + SDL_Color category_color = {static_cast(text_r), static_cast(text_g), static_cast(text_b), 255}; + + Color ball_color = theme_mgr_->getInterpolatedColor(0); + SDL_Color content_color = {static_cast(ball_color.r), static_cast(ball_color.g), static_cast(ball_color.b), 255}; + + // Configuración de espaciado + int line_height = text_renderer_->getTextHeight(); + int padding = 25; // Equilibrio entre espacio y márgenes + int column_width = (box_size_ - padding * 3) / 2; // Ancho de cada columna (2 columnas) + + int current_x = box_x_ + padding; + int current_y = box_y_ + padding; + int current_column = 0; // 0 = izquierda, 1 = derecha + + // Título principal + const char* title = "CONTROLES - ViBe3 Physics"; + int title_width = text_renderer_->getTextWidthPhysical(title); + text_renderer_->printAbsolute( + box_x_ + box_size_ / 2 - title_width / 2, + current_y, + title, + category_color + ); + current_y += line_height * 2; // Espacio después del título + + // Guardar Y inicial de contenido (después del título) + int content_start_y = current_y; + + // Renderizar cada línea + for (const auto& binding : key_bindings_) { + // Si es un separador (descripción vacía), cambiar de columna + if (binding.description[0] == '\0' && binding.key[0] == '\0') { + if (current_column == 0) { + // Cambiar a columna derecha + current_column = 1; + current_x = box_x_ + padding + column_width + padding; + current_y = content_start_y; // Reset Y a posición inicial de contenido + } + continue; + } + + // Si es un encabezado de categoría (descripción vacía pero key no vacía) + if (binding.description[0] == '\0') { + // Renderizar encabezado con color de categoría + text_renderer_->printAbsolute( + current_x, + current_y, + binding.key, + category_color + ); + current_y += line_height + 2; // Espacio extra después de encabezado + continue; + } + + // Renderizar tecla con color de contenido + text_renderer_->printAbsolute( + current_x, + current_y, + binding.key, + content_color + ); + + // Renderizar descripción con color de contenido + int key_width = text_renderer_->getTextWidthPhysical(binding.key); + text_renderer_->printAbsolute( + current_x + key_width + 10, // Espacio entre tecla y descripción + current_y, + binding.description, + content_color + ); + + current_y += line_height; + + // Si nos pasamos del borde inferior del recuadro, cambiar de columna + if (current_y > box_y_ + box_size_ - padding && current_column == 0) { + current_column = 1; + current_x = box_x_ + padding + column_width + padding; + current_y = content_start_y; // Reset Y a inicio de contenido + } + } +} diff --git a/source/ui/help_overlay.h b/source/ui/help_overlay.h new file mode 100644 index 0000000..6695b32 --- /dev/null +++ b/source/ui/help_overlay.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +#include +#include + +class ThemeManager; +class TextRenderer; + +/** + * @class HelpOverlay + * @brief Overlay de ayuda con listado de controles de teclado + * + * Muestra un recuadro cuadrado centrado con todas las teclas y sus funciones. + * Usa los colores del tema actual (como las notificaciones). + * Toggle on/off con tecla H. La simulación continúa en el fondo. + */ +class HelpOverlay { + public: + HelpOverlay(); + ~HelpOverlay(); + + /** + * @brief Inicializa el overlay con renderer y theme manager + */ + void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height); + + /** + * @brief Renderiza el overlay si está visible + */ + void render(SDL_Renderer* renderer); + + /** + * @brief Actualiza dimensiones físicas de ventana (zoom, fullscreen, etc.) + */ + void updatePhysicalWindowSize(int physical_width, int physical_height); + + /** + * @brief Toggle visibilidad del overlay + */ + void toggle() { visible_ = !visible_; } + + /** + * @brief Consulta si el overlay está visible + */ + bool isVisible() const { return visible_; } + + private: + SDL_Renderer* renderer_; + ThemeManager* theme_mgr_; + TextRenderer* text_renderer_; // Renderer de texto para la ayuda + int physical_width_; + int physical_height_; + bool visible_; + + // Dimensiones calculadas del recuadro (90% de dimensión menor, cuadrado, centrado) + int box_size_; + int box_x_; + int box_y_; + + // Calcular dimensiones del recuadro según tamaño de ventana + void calculateBoxDimensions(); + + // Renderizar texto de ayuda dentro del recuadro + void renderHelpText(); + + // Estructura para par tecla-descripción + struct KeyBinding { + const char* key; + const char* description; + }; + + // Lista de todos los controles (se llena en constructor) + std::vector key_bindings_; +}; diff --git a/source/ui/ui_manager.cpp b/source/ui/ui_manager.cpp index dadba3f..e75b51c 100644 --- a/source/ui/ui_manager.cpp +++ b/source/ui/ui_manager.cpp @@ -10,12 +10,14 @@ #include "../text/textrenderer.h" // for TextRenderer #include "../theme_manager.h" // for ThemeManager #include "notifier.h" // for Notifier +#include "help_overlay.h" // for HelpOverlay UIManager::UIManager() : text_renderer_(nullptr) , text_renderer_debug_(nullptr) , text_renderer_notifier_(nullptr) , notifier_(nullptr) + , help_overlay_(nullptr) , show_debug_(false) , show_text_(true) , text_() @@ -38,6 +40,7 @@ UIManager::~UIManager() { delete text_renderer_debug_; delete text_renderer_notifier_; delete notifier_; + delete help_overlay_; } void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, @@ -63,6 +66,10 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager, notifier_->init(renderer, text_renderer_notifier_, theme_manager_, physical_width, physical_height); + // Crear y configurar sistema de ayuda (overlay) + help_overlay_ = new HelpOverlay(); + help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height); + // Inicializar FPS counter fps_last_time_ = SDL_GetTicks(); fps_frame_count_ = 0; @@ -114,12 +121,23 @@ void UIManager::render(SDL_Renderer* renderer, // Renderizar notificaciones (siempre al final, sobre todo lo demás) notifier_->render(); + + // Renderizar ayuda (siempre última, sobre todo incluso notificaciones) + if (help_overlay_) { + help_overlay_->render(renderer); + } } void UIManager::toggleDebug() { show_debug_ = !show_debug_; } +void UIManager::toggleHelp() { + if (help_overlay_) { + help_overlay_->toggle(); + } +} + void UIManager::showNotification(const std::string& text, Uint64 duration) { if (duration == 0) { duration = NOTIFICATION_DURATION; @@ -135,6 +153,9 @@ void UIManager::updatePhysicalWindowSize(int width, int height) { physical_window_width_ = width; physical_window_height_ = height; notifier_->updateWindowSize(width, height); + if (help_overlay_) { + help_overlay_->updatePhysicalWindowSize(width, height); + } } void UIManager::setTextObsolete(const std::string& text, int pos, int current_screen_width) {