Problemas resueltos: - En modo F3 (letterbox), el overlay se centraba en pantalla física en lugar de en el viewport visible, quedando desplazado - Al salir de F3 a ventana, el overlay seguía roto - Padding inferior no se respetaba correctamente Cambios implementados: 1. render() ahora usa SDL_GetRenderViewport() para obtener área visible 2. Centrado calculado dentro del viewport (con offset de barras negras) 3. toggleFullscreen() restaura tamaño de ventana al salir de F3 4. Padding check movido ANTES de escribir línea (>= en lugar de >) 5. Debug logging añadido para diagnóstico de dimensiones Resultado: ✅ Overlay centrado correctamente en F3 letterbox ✅ Overlay se regenera correctamente al salir de F3 ✅ Padding inferior respetado en columna 0 Pendiente: - Columna 2 (índice 1) todavía no respeta padding inferior - Verificar que F4 (real fullscreen) siga funcionando correctamente 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
479 lines
18 KiB
C++
479 lines
18 KiB
C++
#include "help_overlay.h"
|
|
|
|
#include <algorithm> // 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),
|
|
column1_width_(0),
|
|
column2_width_(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::toggle() {
|
|
visible_ = !visible_;
|
|
SDL_Log("HelpOverlay::toggle() - visible=%s, box_pos=(%d,%d), box_size=%dx%d, physical=%dx%d",
|
|
visible_ ? "TRUE" : "FALSE", box_x_, box_y_, box_width_, box_height_,
|
|
physical_width_, physical_height_);
|
|
}
|
|
|
|
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);
|
|
|
|
SDL_Log("HelpOverlay::initialize() - physical=%dx%d, font_size=%d", physical_width, physical_height, font_size);
|
|
|
|
calculateBoxDimensions();
|
|
|
|
SDL_Log("HelpOverlay::initialize() - AFTER calculateBoxDimensions: box_pos=(%d,%d), box_size=%dx%d",
|
|
box_x_, box_y_, box_width_, box_height_);
|
|
}
|
|
|
|
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);
|
|
|
|
// NOTA: NO recalcular dimensiones aquí porque physical_width_ y physical_height_
|
|
// pueden tener valores antiguos. updatePhysicalWindowSize() se llamará después
|
|
// con las dimensiones correctas y recalculará todo apropiadamente.
|
|
|
|
// Marcar textura para regeneración completa
|
|
texture_needs_rebuild_ = true;
|
|
}
|
|
|
|
void HelpOverlay::updateAll(int font_size, int physical_width, int physical_height) {
|
|
SDL_Log("HelpOverlay::updateAll() - INPUT: font_size=%d, physical=%dx%d",
|
|
font_size, physical_width, physical_height);
|
|
SDL_Log("HelpOverlay::updateAll() - BEFORE: box_pos=(%d,%d), box_size=%dx%d",
|
|
box_x_, box_y_, box_width_, box_height_);
|
|
|
|
// Actualizar dimensiones físicas PRIMERO
|
|
physical_width_ = physical_width;
|
|
physical_height_ = physical_height;
|
|
|
|
// Reinicializar text renderer con nuevo tamaño (si cambió)
|
|
if (text_renderer_) {
|
|
text_renderer_->reinitialize(font_size);
|
|
}
|
|
|
|
// Recalcular dimensiones del box con nuevo font y nuevas dimensiones
|
|
calculateBoxDimensions();
|
|
|
|
// Marcar textura para regeneración completa
|
|
texture_needs_rebuild_ = true;
|
|
|
|
SDL_Log("HelpOverlay::updateAll() - AFTER: box_pos=(%d,%d), box_size=%dx%d",
|
|
box_x_, box_y_, box_width_, box_height_);
|
|
}
|
|
|
|
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 (no tiene key ni description)
|
|
if (binding.key[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
int line_width = 0;
|
|
|
|
if (binding.description[0] == '\0') {
|
|
// Es un encabezado (solo tiene key, sin description)
|
|
line_width = text_renderer_->getTextWidthPhysical(binding.key);
|
|
} else {
|
|
// Es una línea normal con key + description
|
|
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
|
int desc_width = text_renderer_->getTextWidthPhysical(binding.description);
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Almacenar anchos de columnas en miembros para uso posterior
|
|
column1_width_ = max_col1_width;
|
|
column2_width_ = max_col2_width;
|
|
|
|
// Ancho total: 2 columnas + 3 paddings (izq, medio, der)
|
|
max_width = max_col1_width + max_col2_width + padding * 3;
|
|
|
|
// Altura: contar líneas REALES en cada columna
|
|
int col1_lines = 0;
|
|
int col2_lines = 0;
|
|
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 no cuenta como línea
|
|
if (binding.key[0] == '\0') {
|
|
continue;
|
|
}
|
|
|
|
// Contar línea (ya sea encabezado o contenido)
|
|
if (current_column == 0) {
|
|
col1_lines++;
|
|
} else {
|
|
col2_lines++;
|
|
}
|
|
}
|
|
|
|
// Usar la columna más larga para calcular altura
|
|
int max_column_lines = std::max(col1_lines, col2_lines);
|
|
|
|
// Altura: título (2 líneas) + contenido + padding superior e inferior
|
|
total_height = line_height * 2 + max_column_lines * line_height + padding * 2;
|
|
}
|
|
|
|
void HelpOverlay::calculateBoxDimensions() {
|
|
SDL_Log("HelpOverlay::calculateBoxDimensions() START - physical=%dx%d", physical_width_, physical_height_);
|
|
|
|
// Calcular dimensiones necesarias según el texto
|
|
int text_width, text_height;
|
|
calculateTextDimensions(text_width, text_height);
|
|
|
|
SDL_Log("HelpOverlay::calculateBoxDimensions() - text_width=%d, text_height=%d, col1_width=%d, col2_width=%d",
|
|
text_width, text_height, column1_width_, column2_width_);
|
|
|
|
// Usar directamente el ancho y altura calculados según el contenido
|
|
box_width_ = text_width;
|
|
|
|
// Altura: 90% de altura física o altura calculada, el que sea menor
|
|
int max_height = static_cast<int>(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;
|
|
|
|
SDL_Log("HelpOverlay::calculateBoxDimensions() END - box_pos=(%d,%d), box_size=%dx%d, max_height=%d",
|
|
box_x_, box_y_, box_width_, box_height_, max_height);
|
|
}
|
|
|
|
void HelpOverlay::rebuildCachedTexture() {
|
|
if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
|
|
|
|
SDL_Log("HelpOverlay::rebuildCachedTexture() - Regenerando textura: box_size=%dx%d, box_pos=(%d,%d)",
|
|
box_width_, box_height_, box_x_, box_y_);
|
|
|
|
// 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<float>(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<float>(box_width_), static_cast<float>(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<float>(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<Uint8>(text_r), static_cast<Uint8>(text_g), static_cast<Uint8>(text_b), 255};
|
|
|
|
Color ball_color = theme_mgr_->getInterpolatedColor(0);
|
|
SDL_Color content_color = {static_cast<Uint8>(ball_color.r), static_cast<Uint8>(ball_color.g), static_cast<Uint8>(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<Uint8>(notif_bg_r), static_cast<Uint8>(notif_bg_g), static_cast<Uint8>(notif_bg_b), 255};
|
|
|
|
// Configuración de espaciado
|
|
int line_height = text_renderer_->getTextHeight();
|
|
int padding = 25;
|
|
|
|
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 + column1_width_ + padding; // Usar ancho real de columna 1
|
|
current_y = content_start_y;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// CHECK PADDING INFERIOR ANTES de escribir la línea (solo para columna 0)
|
|
// Verificar si la PRÓXIMA línea cabrá dentro del box con padding inferior
|
|
if (current_y + line_height >= box_height_ - padding && current_column == 0) {
|
|
current_column = 1;
|
|
current_x = padding + column1_width_ + padding;
|
|
current_y = content_start_y;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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<Uint8>(notif_bg_r), static_cast<Uint8>(notif_bg_g), static_cast<Uint8>(notif_bg_b), 255};
|
|
SDL_Color current_category = {static_cast<Uint8>(text_r), static_cast<Uint8>(text_g), static_cast<Uint8>(text_b), 255};
|
|
SDL_Color current_content = {static_cast<Uint8>(ball_color.r), static_cast<Uint8>(ball_color.g), static_cast<Uint8>(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);
|
|
|
|
// Obtener viewport actual (en modo letterbox F3 tiene offset para centrar imagen)
|
|
SDL_Rect viewport;
|
|
SDL_GetRenderViewport(renderer, &viewport);
|
|
|
|
// Calcular posición centrada dentro del VIEWPORT, no de la pantalla física
|
|
// viewport.w y viewport.h son las dimensiones del área visible
|
|
// viewport.x y viewport.y son el offset de las barras negras
|
|
int centered_x = viewport.x + (viewport.w - box_width_) / 2;
|
|
int centered_y = viewport.y + (viewport.h - box_height_) / 2;
|
|
|
|
SDL_Log("HelpOverlay::render() - viewport=(%d,%d,%dx%d), centered_pos=(%d,%d), box_size=%dx%d",
|
|
viewport.x, viewport.y, viewport.w, viewport.h, centered_x, centered_y, box_width_, box_height_);
|
|
|
|
// Renderizar la textura cacheada centrada en el viewport
|
|
SDL_FRect dest_rect;
|
|
dest_rect.x = static_cast<float>(centered_x);
|
|
dest_rect.y = static_cast<float>(centered_y);
|
|
dest_rect.w = static_cast<float>(box_width_);
|
|
dest_rect.h = static_cast<float>(box_height_);
|
|
|
|
SDL_RenderTexture(renderer, cached_texture_, nullptr, &dest_rect);
|
|
}
|