Files
vibe3_physics/source/ui/help_overlay.cpp
Sergio Valor bcceb94c9e fix: Help Overlay centrado correcto en modo F3 letterbox
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>
2025-10-17 07:30:31 +02:00

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);
}