#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_width_(0), box_height_(0), box_x_(0), box_y_(0), cached_texture_(nullptr), last_category_color_({0, 0, 0, 255}), last_content_color_({0, 0, 0, 255}), last_bg_color_({0, 0, 0, 255}), texture_needs_rebuild_(true) { // 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() { // Destruir textura cacheada si existe if (cached_texture_) { SDL_DestroyTexture(cached_texture_); cached_texture_ = nullptr; } delete text_renderer_; } void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size) { renderer_ = renderer; theme_mgr_ = theme_mgr; physical_width_ = physical_width; physical_height_ = physical_height; // Crear renderer de texto con tamaño dinámico text_renderer_ = new TextRenderer(); text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true); calculateBoxDimensions(); } void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_height) { physical_width_ = physical_width; physical_height_ = physical_height; calculateBoxDimensions(); // Marcar textura para regeneración (dimensiones han cambiado) texture_needs_rebuild_ = true; } void HelpOverlay::reinitializeFontSize(int new_font_size) { if (!text_renderer_) return; // Reinicializar text renderer con nuevo tamaño text_renderer_->reinitialize(new_font_size); // Recalcular dimensiones del box (el texto ahora tiene distinto tamaño) calculateBoxDimensions(); // Marcar textura para regeneración completa texture_needs_rebuild_ = true; } void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) { if (!text_renderer_) { max_width = 0; total_height = 0; return; } int line_height = text_renderer_->getTextHeight(); int padding = 25; // Calcular ancho máximo por columna int max_col1_width = 0; int max_col2_width = 0; int current_column = 0; for (const auto& binding : key_bindings_) { // Cambio de columna if (strcmp(binding.key, "[new_col]") == 0) { current_column = 1; continue; } // Separador vacío o encabezado if (binding.description[0] == '\0') { continue; } // Calcular ancho de esta línea: key + espacio + description int key_width = text_renderer_->getTextWidthPhysical(binding.key); int desc_width = text_renderer_->getTextWidthPhysical(binding.description); int line_width = key_width + 10 + desc_width; // 10px de separación // Actualizar máximo de columna correspondiente if (current_column == 0) { max_col1_width = std::max(max_col1_width, line_width); } else { max_col2_width = std::max(max_col2_width, line_width); } } // Ancho total: 2 columnas + 3 paddings (izq, medio, der) max_width = max_col1_width + max_col2_width + padding * 3; // Altura: contar líneas y calcular int num_lines = 0; for (const auto& binding : key_bindings_) { if (strcmp(binding.key, "[new_col]") != 0) { num_lines++; } } // Altura: título + espacio + líneas de contenido total_height = line_height * 2 + (num_lines / 2 + 2) * line_height + padding * 2; } void HelpOverlay::calculateBoxDimensions() { // Calcular dimensiones necesarias según el texto int text_width, text_height; calculateTextDimensions(text_width, text_height); // Ancho mínimo: 90% de dimensión menor (como antes, para compatibilidad) int min_dimension = std::min(physical_width_, physical_height_); int min_width = static_cast(min_dimension * 0.9f); // Usar el mayor entre ancho calculado y ancho mínimo box_width_ = std::max(text_width, min_width); // Altura: 90% de altura física o altura calculada, el que sea menor int max_height = static_cast(physical_height_ * 0.9f); box_height_ = std::min(text_height, max_height); // Centrar en pantalla box_x_ = (physical_width_ - box_width_) / 2; box_y_ = (physical_height_ - box_height_) / 2; } void HelpOverlay::rebuildCachedTexture() { if (!renderer_ || !theme_mgr_ || !text_renderer_) return; // Destruir textura anterior si existe if (cached_texture_) { SDL_DestroyTexture(cached_texture_); cached_texture_ = nullptr; } // Crear nueva textura del tamaño del overlay cached_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, box_width_, box_height_); if (!cached_texture_) { SDL_Log("Error al crear textura cacheada: %s", SDL_GetError()); return; } // Habilitar alpha blending en la textura SDL_SetTextureBlendMode(cached_texture_, SDL_BLENDMODE_BLEND); // Guardar render target actual SDL_Texture* prev_target = SDL_GetRenderTarget(renderer_); // Cambiar render target a la textura cacheada SDL_SetRenderTarget(renderer_, cached_texture_); // Limpiar textura (completamente transparente) SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0); SDL_RenderClear(renderer_); // Habilitar alpha blending SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND); // Obtener colores actuales del tema int notif_bg_r, notif_bg_g, notif_bg_b; theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b); // Renderizar fondo del overlay a la textura float alpha = 0.85f; SDL_Vertex bg_vertices[4]; float r = notif_bg_r / 255.0f; float g = notif_bg_g / 255.0f; float b = notif_bg_b / 255.0f; // Vértices del fondo (posición relativa 0,0 porque estamos renderizando a textura) bg_vertices[0].position = {0, 0}; bg_vertices[0].tex_coord = {0.0f, 0.0f}; bg_vertices[0].color = {r, g, b, alpha}; bg_vertices[1].position = {static_cast(box_width_), 0}; bg_vertices[1].tex_coord = {1.0f, 0.0f}; bg_vertices[1].color = {r, g, b, alpha}; bg_vertices[2].position = {static_cast(box_width_), static_cast(box_height_)}; bg_vertices[2].tex_coord = {1.0f, 1.0f}; bg_vertices[2].color = {r, g, b, alpha}; bg_vertices[3].position = {0, static_cast(box_height_)}; bg_vertices[3].tex_coord = {0.0f, 1.0f}; bg_vertices[3].color = {r, g, b, alpha}; int bg_indices[6] = {0, 1, 2, 2, 3, 0}; SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6); // Renderizar texto del overlay (ajustando coordenadas para que sean relativas a 0,0) // Necesito renderizar el texto igual que en renderHelpText() pero con coordenadas ajustadas // Obtener colores para el texto 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}; // Guardar colores actuales para comparación futura last_category_color_ = category_color; last_content_color_ = content_color; last_bg_color_ = {static_cast(notif_bg_r), static_cast(notif_bg_g), static_cast(notif_bg_b), 255}; // Configuración de espaciado (misma que renderHelpText()) int line_height = text_renderer_->getTextHeight(); int padding = 25; int column_width = (box_width_ - padding * 3) / 2; int current_x = padding; // Coordenadas relativas a la textura (0,0) int current_y = padding; int current_column = 0; // Título principal const char* title = "CONTROLES - ViBe3 Physics"; int title_width = text_renderer_->getTextWidthPhysical(title); text_renderer_->printAbsolute(box_width_ / 2 - title_width / 2, current_y, title, category_color); current_y += line_height * 2; int content_start_y = current_y; // Renderizar cada línea for (const auto& binding : key_bindings_) { if (strcmp(binding.key, "[new_col]") == 0 && binding.description[0] == '\0') { if (current_column == 0) { current_column = 1; current_x = padding + column_width + padding; current_y = content_start_y; } continue; } if (binding.description[0] == '\0') { text_renderer_->printAbsolute(current_x, current_y, binding.key, category_color); current_y += line_height + 2; continue; } text_renderer_->printAbsolute(current_x, current_y, binding.key, content_color); int key_width = text_renderer_->getTextWidthPhysical(binding.key); text_renderer_->printAbsolute(current_x + key_width + 10, current_y, binding.description, content_color); current_y += line_height; if (current_y > box_height_ - padding && current_column == 0) { current_column = 1; current_x = padding + column_width + padding; current_y = content_start_y; } } // Restaurar render target original SDL_SetRenderTarget(renderer_, prev_target); // Marcar que ya no necesita rebuild texture_needs_rebuild_ = false; } void HelpOverlay::render(SDL_Renderer* renderer) { if (!visible_) return; // Obtener colores actuales del tema int notif_bg_r, notif_bg_g, notif_bg_b; theme_mgr_->getCurrentNotificationBackgroundColor(notif_bg_r, notif_bg_g, notif_bg_b); int text_r, text_g, text_b; theme_mgr_->getCurrentThemeTextColor(text_r, text_g, text_b); Color ball_color = theme_mgr_->getInterpolatedColor(0); // Crear colores actuales para comparación SDL_Color current_bg = {static_cast(notif_bg_r), static_cast(notif_bg_g), static_cast(notif_bg_b), 255}; SDL_Color current_category = {static_cast(text_r), static_cast(text_g), static_cast(text_b), 255}; SDL_Color current_content = {static_cast(ball_color.r), static_cast(ball_color.g), static_cast(ball_color.b), 255}; // Detectar si los colores han cambiado significativamente (umbral: 5/255) constexpr int COLOR_CHANGE_THRESHOLD = 5; bool colors_changed = (abs(current_bg.r - last_bg_color_.r) > COLOR_CHANGE_THRESHOLD || abs(current_bg.g - last_bg_color_.g) > COLOR_CHANGE_THRESHOLD || abs(current_bg.b - last_bg_color_.b) > COLOR_CHANGE_THRESHOLD || abs(current_category.r - last_category_color_.r) > COLOR_CHANGE_THRESHOLD || abs(current_category.g - last_category_color_.g) > COLOR_CHANGE_THRESHOLD || abs(current_category.b - last_category_color_.b) > COLOR_CHANGE_THRESHOLD || abs(current_content.r - last_content_color_.r) > COLOR_CHANGE_THRESHOLD || abs(current_content.g - last_content_color_.g) > COLOR_CHANGE_THRESHOLD || abs(current_content.b - last_content_color_.b) > COLOR_CHANGE_THRESHOLD); // Regenerar textura si es necesario (colores cambiaron O flag de rebuild activo) if (texture_needs_rebuild_ || colors_changed || !cached_texture_) { rebuildCachedTexture(); } // Si no hay textura cacheada (error), salir if (!cached_texture_) return; // CRÍTICO: Habilitar alpha blending para que la transparencia funcione SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); // Renderizar la textura cacheada en la posición del overlay SDL_FRect dest_rect; dest_rect.x = static_cast(box_x_); dest_rect.y = static_cast(box_y_); dest_rect.w = static_cast(box_width_); dest_rect.h = static_cast(box_height_); SDL_RenderTexture(renderer, cached_texture_, nullptr, &dest_rect); }