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>
This commit is contained in:
2025-10-17 07:30:31 +02:00
parent 1b3d32ba84
commit bcceb94c9e
4 changed files with 190 additions and 10 deletions

128
RULES.md Normal file
View File

@@ -0,0 +1,128 @@
Documento de especificaciones de ViBe3 Physics
# Codigo
* Se preferira el uso de #pragma once a #ifndef
* Se preferira el uso de C++ frente a C
* Se preferirá el uso de verisiones mas moderdas de C++ frente a las mas viejas, es decir, C++20 frente a C++17, por ejemplo
* Se preferirá el uso de smart pointers frente a new/delete y sobretodo antes que malloc/free
* Los archivos de cabecera que definan clases, colocaran primero la parte publica y luego la privada. Agruparan los metodos por categorias. Todas las variables, constantes, estructuras, enumeraciones, metodos, llevaran el comentario a la derecha
* Se respetarán las reglas definidas en los ficheros .clang-tidy y .clang-format que hay en la raíz o en las subcarpetas
# Funcionamiento
* El programa tiene modos de funcionamiento (AppMode). El funcionamiento de cada uno de ellos se describirá mas adelante. Son estados exclusivos que van automatizando cambios en el SimulationMode, Theme y Scene y serian:
* SANDBOX
* DEMO
* DEMO LITE
* LOGO
* LOGO LITE
* El progama tiene otros modos de funcionamiento (SimulationMode). El funcionamiento de cada uno de ellos se describirá mas adelante. Son estados exclusivos:
* PHYISICS
* FIGURE
* BOIDS
* El programa tiene un gestor de temas (Theme) que cambia los colores de lo que se ve en pantalla. Hay temas estáticos y dinamicos. El cambio de tema se realiza mediante LERP y no afecta en nada ni al AppMode ni al SimulationMode, es decir, no modifica sus estados.
* El programa tiene escenarios (Scene). Cada escena tiene un numero de pelotas. Cuando se cambia el escenario, se elimina el vector de pelotas y se crea uno nuevo. En funcion del SimulationMode actual se inicializan las pelotas de manera distinta:
* PHYSICS: Se crean todas las pelotas cerca de la parte superior de la pantalla distribuidas en el 75% central del eje X (es como está ahora)
* FIGURE: Se crean todas las pelotas en el punto central de la pantalla
* BOIDS: Se crean todas las pelotas en posiciones al azar de la pantalla con velocidades y direcciones aleatorias
* El cambio de SimulationMode ha de preservar la inercia (velocidad, aceleracion, direccion) de cada pelota. El cambio se produce tanto de forma manual (pulsacion de una tecla por el usuario) como de manera automatica (cualquier AppMode que no sea SANDBOX)
* PHYSICS a FIGURE:
* Pulsando la tecla de la figura correspondiente
* Pulsando la tecla F (ultima figura seleccionada)
* PHYSICS a BOIDS:
* Pulsando la tecla B
* FIGURE a PHYSICS:
* Pulsando los cursores: Gravedad ON en la direccion del cursor
* Pulsando la tecla G: Gravedad OFF
* Pulsando la tecla F: Ultima gravedad seleccionada (direccion o OFF)
* FIGURE a BOIDS:
* Pulsando la tecla B
* BOIDS a PHYSICS:
* Pulsando la tecla G: Gravedad OFF
* Pulsando los cursores: Gravedad ON en la direccion del cursor
* BOIDS a FIGURE:
* Pulsando la tecla de la figura
* Pulsando la tecla F (ultima figura)
# AppMode
* SANDBOX
* No hay ningun automatismo. El usuario va pulsando teclas para ejecutar acciones.
* Si pulsa una de estas teclas, cambia de modo:
* D: DEMO
* L: DEMO LITE
* K: LOGO
* DEMO
* En el modo DEMO el programa va cambiando el SimulationMode de manera automatica (como está ahora es correcto)
* Se inicializa con un Theme al azar, Scene al azar, SimulationMode al azar. Restringido FIGURE->PNG_SHAPE
* Va cambiando de Theme
* Va cambiando de Scene
* Cambia la escala de la Figure
* Cambia el Sprite de las pelotas
* NO PUEDE cambiar a la figura PNG_SHAPE
* Eventualmente puede cambiar de manera automatica a LOGO LITE, sin restricciones
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo DEMO
* El usuario puede cambiar de AppMode pulsando:
* D: SANDBOX
* L: DEMO LITE
* K: LOGO
* DEMO LITE
* En el modo DEMO el programa va cambiando el SimulationMode de manera automatica (como está ahora es correcto)
* Se inicializa con un Theme al azar, Scene al azar, SimulationMode al azar. Restringido FIGURE->PNG_SHAPE
* Este modo es exactamente igual a DEMO pero NO PUEDE:
* Cambiar de Scene
* Cambiar de Theme
* Cambiar el Sprite de las pelotas
* Eventualmente puede cambiar de manera automatica a LOGO LITE, sin restricciones
* NO PUEDE cambiar a la figura PNG_SHAPE
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo DEMO LITE
* El usuario puede cambiar de AppMode pulsando:
* D: DEMO
* L: SANDBOX
* K: LOGO
* LOGO
* Se inicializa con la Scene de 5.000 pelotas, con el tamaño de Sprite->Small, con SimulationMode en FIGURE->PNG_SHAPE, con un tema al azar de los permitidos
* No cambia de Scene
* No cambia el tamaño de Sprite
* No cambia la escala de FIGURE
* Los temas permitidos son MONOCROMO, LAVANDA, CARMESI, ESMERALDA o cualquiera de los temas dinamicos
* En este modo SOLO aparece la figura PNG_SHAPE
* Solo cambiara a los temas permitidos
* Cambia el SimulationMode de PHYSICS a FIGURE (como hace ahora) pero no a BOIDS. BOIDS prohibido
* El usuario puede cambiar el SimulationMode, el Theme o el Scene. Esto no hace que se salga del modo LOGO. Incluso puede poner un Theme no permitido o otro Scene.
* El automatismo no cambia nunca de Theme así que se mantiene el del usuario.
* El automatismo no cambia nunca de Scene asi que se mantiene el del usuario.
* El usuario puede cambiar de AppMode pulsando:
* D: DEMO
* L: DEMO LITE
* K: SANDBOX
* B: SANDBOX->BOIDS
* LOGO LITE
* Este modo es exactamente igual al modo LOGO pero con unas pequeñas diferencias:
* Solo se accede a el de manera automatica, el usuario no puede invocarlo. No hay tecla
* Como se accede de manera automatica solo se puede llegar a él desde DEMO o DEMO LITE. Hay que guardar el estado en el que se encontraba AppMode, EngindeMode, Scene, Theme, Sprite, Scale... etc
* Este modo tiene una muy alta probabilidad de terminar, volviendo al estado anterior desde donde se invocó.
* El usuario puede cambiar de AppMode pulsando:
* D: Si el modo anterior era DEMO -> SANDBOX, else -> DEMO)
* L: Si el modo anterior era DEMO LITE -> SANDBOX, else -> DEMO LITE)
* K: LOGO
* B: SANDBOX->BOIDS
# Debug Hud
* En el debug hud hay que añadir que se vea SIEMPRE el AppMode (actualmente aparece centrado, hay que ponerlo a la izquierda) y no solo cietos AppModes
* Tiene que aparecer tambien el SimulationMode
* El modo de Vsync
* El modo de escalado entero, stretched, ventana
* la resolucion fisica
* la resolucion logica
* el refresco del panel
* El resto de cosas que salen
# Ventana de ayuda
* La ventana de ayuda actualmente es cuadrada
* Esa es la anchura minima que ha de tener
* Hay que ver cual es la linea mas larga, multiplicarla por el numero de columnas, añadirle los paddings y que ese sea el nuevo ancho
* Actualmente se renderiza a cada frame. El rendimiento cae de los 1200 frames por segundo a 200 frames por segundo. Habria que renderizarla a una textura o algo. El problema es que el cambio de Theme con LERP afecta a los colores de la ventana. Hay que investigar qué se puede hacer.
# Bugs actuales
* En el modo LOGO, si se pulsa un cursor, se activa la gravedad y deja de funcionar los automatismos. Incluso he llegado a ver como sale solo del modo LOGO sin pulsar nada
* En el modo BOIDS, pulsar la G activa la gravedad. La G deberia pasar al modo PHYSICS con la gravedad en OFF y que las pelotas mantuvieran el momento/inercia

View File

@@ -744,6 +744,12 @@ void Engine::toggleFullscreen() {
fullscreen_enabled_ = !fullscreen_enabled_;
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
// Si acabamos de salir de fullscreen, restaurar tamaño de ventana
if (!fullscreen_enabled_) {
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Actualizar dimensiones físicas después del cambio
updatePhysicalWindowSize();
}

View File

@@ -86,6 +86,13 @@ HelpOverlay::~HelpOverlay() {
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;
@@ -96,7 +103,12 @@ void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, in
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) {
@@ -123,6 +135,11 @@ void HelpOverlay::reinitializeFontSize(int new_font_size) {
}
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;
@@ -137,6 +154,9 @@ void HelpOverlay::updateAll(int font_size, int physical_width, int physical_heig
// 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) {
@@ -226,10 +246,15 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
}
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;
@@ -240,11 +265,17 @@ void HelpOverlay::calculateBoxDimensions() {
// 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_);
@@ -354,6 +385,14 @@ void HelpOverlay::rebuildCachedTexture() {
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;
@@ -365,12 +404,6 @@ void HelpOverlay::rebuildCachedTexture() {
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 + column1_width_ + padding; // Usar ancho real de columna 1
current_y = content_start_y;
}
}
// Restaurar render target original
@@ -421,10 +454,23 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
// 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
// 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>(box_x_);
dest_rect.y = static_cast<float>(box_y_);
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_);

View File

@@ -52,7 +52,7 @@ class HelpOverlay {
/**
* @brief Toggle visibilidad del overlay
*/
void toggle() { visible_ = !visible_; }
void toggle();
/**
* @brief Consulta si el overlay está visible