#include "help_overlay.h" #include // for std::min #include "../text/textrenderer.h" #include "../theme_manager.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"}, {"CURSORES", "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"}, {"[new_col]", ""}, // 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"}, {"", ""}, // Separador // COLUMNA 2: MODOS {"MODOS", ""}, {"D", "Modo DEMO"}, {"Shift+D", "Pausar tema dinámico"}, {"L", "Modo DEMO LITE"}, {"K", "Modo LOGO (easter egg)"}, {"", ""}, // Separador // 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/FunnelSans-Regular.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.85f; 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 (strcmp(binding.key, "[new_col]") == 0 && binding.description[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 } } }