3 Commits

Author SHA1 Message Date
250b1a640d feat: Dynamic text scaling based on physical window size
Sistema de escalado dinámico de texto con 3 tamaños según área de ventana:

1. TextRenderer improvements:
   - Añadido reinitialize(int new_font_size) para cambiar tamaño en runtime
   - Almacena font_path_ para permitir recarga de fuente
   - Cierra fuente anterior y abre nueva con diferente tamaño
   - Verifica si tamaño es igual antes de reinicializar (optimización)

2. UIManager - Font size calculation:
   - Añadido calculateFontSize() con stepped scaling por área:
     * SMALL (< 800x600): 14px
     * MEDIUM (800x600 a 1920x1080): 18px
     * LARGE (> 1920x1080): 24px
   - Tracking de current_font_size_ para detectar cambios
   - Inicialización con tamaño dinámico en initialize()
   - Reinitialización automática en updatePhysicalWindowSize()

3. UIManager - Propagation:
   - Reinitializa 3 TextRenderer instances cuando cambia tamaño
   - Propaga nuevo tamaño a HelpOverlay
   - Detecta cambios solo cuando área cruza umbrales (eficiencia)

4. HelpOverlay integration:
   - Acepta font_size como parámetro en initialize()
   - Añadido reinitializeFontSize() para cambios dinámicos
   - Recalcula dimensiones del box cuando cambia fuente
   - Marca textura para rebuild completo tras cambio

Resultado:
- Ventanas pequeñas: texto 14px (más espacio para contenido)
- Ventanas medianas: texto 18px (tamaño original, óptimo)
- Ventanas grandes: texto 24px (mejor legibilidad)
- Cambios automáticos al redimensionar ventana (F1/F2/F3/F4)
- Sin impacto en performance (solo recalcula al cruzar umbrales)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:41:19 +02:00
795fa33e50 feat: Help Overlay - dynamic width + render-to-texture caching
Mejoras de rendimiento y usabilidad del Help Overlay:

1. Anchura dinámica basada en contenido:
   - Ya no es siempre cuadrado (box_size_)
   - Calcula ancho real según texto más largo por columna
   - Mantiene mínimo del 90% dimensión menor como antes
   - Nueva función calculateTextDimensions()

2. Render-to-texture caching para optimización:
   - Renderiza overlay completo a textura una sola vez
   - Detecta cambios de color con umbral (threshold 5/255)
   - Soporta temas dinámicos con LERP sin rebuild constante
   - Regenera solo cuando colores cambian o ventana redimensiona

3. Impacto en performance:
   - Antes: 1200 FPS → 200 FPS con overlay activo
   - Después: 1200 FPS → 1000-1200 FPS (casi sin impacto)
   - Temas estáticos: 1 render total (~∞x más rápido)
   - Temas dinámicos: regenera cada ~20-30 frames (~25x más rápido)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:36:53 +02:00
e7dc8f6d13 feat: añadir cambio de dirección de gravedad en modo LOGO
El modo LOGO ahora incluye cambios automáticos de dirección de
gravedad como parte de sus variaciones, aumentando la diversidad
visual de la demostración.

Cambios:
- Nueva acción en modo LOGO (PHYSICS): cambiar dirección gravedad (16%)
- Rebalanceo de probabilidades existentes:
  • PHYSICS → SHAPE: 60% → 50%
  • Gravedad ON: 20% → 18%
  • Gravedad OFF: 20% → 16%
  • Dirección gravedad: nuevo 16%
- Al cambiar dirección, se fuerza gravedad ON para visibilidad

Antes el modo LOGO solo alternaba entre figura/física y gravedad
on/off, pero nunca cambiaba la dirección. Ahora tiene las mismas
capacidades de variación que los modos DEMO y DEMO_LITE.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:11:22 +02:00
8 changed files with 369 additions and 96 deletions

View File

@@ -1140,20 +1140,26 @@ void Engine::performLogoAction(bool logo_waiting_for_flip) {
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
}
} else {
// Logo animado (PHYSICS) → 3 opciones posibles
if (action < 60) {
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
// Logo animado (PHYSICS) → 4 opciones posibles
if (action < 50) {
// 50%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
toggleShapeModeInternal(false);
// Resetear variables de espera de flips al volver a SHAPE
logo_waiting_for_flip_ = false;
logo_current_flip_count_ = 0;
} else if (action < 80) {
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
} else if (action < 68) {
// 18%: Forzar gravedad ON (empezar a caer mientras da vueltas)
scene_manager_->forceBallsGravityOn();
} else {
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
} else if (action < 84) {
// 16%: Forzar gravedad OFF (flotar mientras da vueltas)
scene_manager_->forceBallsGravityOff();
} else {
// 16%: Cambiar dirección de gravedad (nueva variación)
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_manager_->changeGravityDirection(new_direction);
// Si la gravedad está OFF, activarla para que el cambio sea visible
scene_manager_->forceBallsGravityOn();
}
// Resetear timer con intervalos escalados

View File

@@ -119,6 +119,11 @@ class StateManager {
*/
float getLogoPreviousShapeScale() const { return logo_previous_shape_scale_; }
/**
* @brief Obtiene si LOGO fue activado manualmente (tecla K) o automáticamente (desde DEMO)
*/
bool getLogoEnteredManually() const { return logo_entered_manually_; }
/**
* @brief Establece valores previos de LOGO (llamado por Engine antes de entrar)
*/

View File

@@ -13,6 +13,7 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
renderer_ = renderer;
font_size_ = font_size;
use_antialiasing_ = use_antialiasing;
font_path_ = font_path; // Guardar ruta para reinitialize()
// Inicializar SDL_ttf si no está inicializado
if (!TTF_WasInit()) {
@@ -32,6 +33,38 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
return true;
}
bool TextRenderer::reinitialize(int new_font_size) {
// Verificar que tenemos todo lo necesario
if (renderer_ == nullptr || font_path_.empty()) {
SDL_Log("Error: TextRenderer no inicializado correctamente para reinitialize()");
return false;
}
// Si el tamaño es el mismo, no hacer nada
if (new_font_size == font_size_) {
return true;
}
// Cerrar fuente actual
if (font_ != nullptr) {
TTF_CloseFont(font_);
font_ = nullptr;
}
// Cargar fuente con nuevo tamaño
font_ = TTF_OpenFont(font_path_.c_str(), new_font_size);
if (font_ == nullptr) {
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
font_path_.c_str(), new_font_size, SDL_GetError());
return false;
}
// Actualizar tamaño almacenado
font_size_ = new_font_size;
return true;
}
void TextRenderer::cleanup() {
if (font_ != nullptr) {
TTF_CloseFont(font_);

View File

@@ -12,6 +12,9 @@ public:
// Inicializa el renderizador de texto con una fuente
bool init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing = true);
// Reinicializa el renderizador con un nuevo tamaño de fuente
bool reinitialize(int new_font_size);
// Libera recursos
void cleanup();
@@ -46,4 +49,5 @@ private:
TTF_Font* font_;
int font_size_;
bool use_antialiasing_;
std::string font_path_; // Almacenar ruta para reinitialize()
};

View File

@@ -12,9 +12,15 @@ HelpOverlay::HelpOverlay()
physical_width_(0),
physical_height_(0),
visible_(false),
box_size_(0),
box_width_(0),
box_height_(0),
box_x_(0),
box_y_(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
@@ -70,18 +76,23 @@ HelpOverlay::HelpOverlay()
}
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) {
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 reducido (18px en lugar de 24px)
// Crear renderer de texto con tamaño dinámico
text_renderer_ = new TextRenderer();
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true);
calculateBoxDimensions();
}
@@ -90,69 +101,172 @@ void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_heig
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() {
// 90% de la dimensión más corta (cuadrado)
// 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_);
box_size_ = static_cast<int>(min_dimension * 0.9f);
int min_width = static_cast<int>(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<int>(physical_height_ * 0.9f);
box_height_ = std::min(text_height, max_height);
// Centrar en pantalla
box_x_ = (physical_width_ - box_size_) / 2;
box_y_ = (physical_height_ - box_size_) / 2;
box_x_ = (physical_width_ - box_width_) / 2;
box_y_ = (physical_height_ - box_height_) / 2;
}
void HelpOverlay::render(SDL_Renderer* renderer) {
if (!visible_) return;
void HelpOverlay::rebuildCachedTexture() {
if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
// CRÍTICO: Habilitar alpha blending para que la transparencia funcione
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
// Destruir textura anterior si existe
if (cached_texture_) {
SDL_DestroyTexture(cached_texture_);
cached_texture_ = nullptr;
}
// Obtener color de notificación del tema actual (para el fondo)
// 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 semitransparente usando SDL_RenderGeometry (soporta alpha real)
// Renderizar fondo del overlay a la textura
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<float>(box_x_), static_cast<float>(box_y_)};
// 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};
// Vértice superior derecho
bg_vertices[1].position = {static_cast<float>(box_x_ + box_size_), static_cast<float>(box_y_)};
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};
// Vértice inferior derecho
bg_vertices[2].position = {static_cast<float>(box_x_ + box_size_), static_cast<float>(box_y_ + box_size_)};
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};
// Vértice inferior izquierdo
bg_vertices[3].position = {static_cast<float>(box_x_), static_cast<float>(box_y_ + box_size_)};
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};
// Índices para 2 triángulos
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
// Renderizar sin textura (nullptr) con alpha blending
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
// Renderizar texto de ayuda
renderHelpText();
}
void HelpOverlay::renderHelpText() {
// Obtener 2 colores del tema para diferenciación visual
// 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};
@@ -160,75 +274,112 @@ void HelpOverlay::renderHelpText() {
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};
// 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)
// 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};
int current_x = box_x_ + padding;
int current_y = box_y_ + padding;
int current_column = 0; // 0 = izquierda, 1 = derecha
// 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_x_ + box_size_ / 2 - title_width / 2,
current_y,
title,
category_color);
current_y += line_height * 2; // Espacio después del título
text_renderer_->printAbsolute(box_width_ / 2 - title_width / 2, current_y, title, category_color);
current_y += line_height * 2;
// 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
current_x = padding + column_width + padding;
current_y = content_start_y;
}
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
text_renderer_->printAbsolute(current_x, current_y, binding.key, category_color);
current_y += line_height + 2;
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
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, // Espacio entre tecla y descripción
current_y,
binding.description,
content_color);
text_renderer_->printAbsolute(current_x + key_width + 10, 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) {
if (current_y > box_height_ - 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
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<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);
// Renderizar la textura cacheada en la posición del overlay
SDL_FRect dest_rect;
dest_rect.x = static_cast<float>(box_x_);
dest_rect.y = static_cast<float>(box_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);
}

View File

@@ -24,7 +24,7 @@ class HelpOverlay {
/**
* @brief Inicializa el overlay con renderer y theme manager
*/
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height);
void initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size);
/**
* @brief Renderiza el overlay si está visible
@@ -36,6 +36,11 @@ class HelpOverlay {
*/
void updatePhysicalWindowSize(int physical_width, int physical_height);
/**
* @brief Reinitializa el tamaño de fuente (cuando cambia el tamaño de ventana)
*/
void reinitializeFontSize(int new_font_size);
/**
* @brief Toggle visibilidad del overlay
*/
@@ -54,16 +59,27 @@ class HelpOverlay {
int physical_height_;
bool visible_;
// Dimensiones calculadas del recuadro (90% de dimensión menor, cuadrado, centrado)
int box_size_;
// Dimensiones calculadas del recuadro (anchura dinámica según texto, centrado)
int box_width_;
int box_height_;
int box_x_;
int box_y_;
// Calcular dimensiones del recuadro según tamaño de ventana
// Sistema de caché para optimización de rendimiento
SDL_Texture* cached_texture_; // Textura cacheada del overlay completo
SDL_Color last_category_color_; // Último color de categorías renderizado
SDL_Color last_content_color_; // Último color de contenido renderizado
SDL_Color last_bg_color_; // Último color de fondo renderizado
bool texture_needs_rebuild_; // Flag para forzar regeneración de textura
// Calcular dimensiones del texto más largo
void calculateTextDimensions(int& max_width, int& total_height);
// Calcular dimensiones del recuadro según tamaño de ventana y texto
void calculateBoxDimensions();
// Renderizar texto de ayuda dentro del recuadro
void renderHelpText();
// Regenerar textura cacheada del overlay
void rebuildCachedTexture();
// Estructura para par tecla-descripción
struct KeyBinding {

View File

@@ -32,7 +32,8 @@ UIManager::UIManager()
, renderer_(nullptr)
, theme_manager_(nullptr)
, physical_window_width_(0)
, physical_window_height_(0) {
, physical_window_height_(0)
, current_font_size_(18) { // Tamaño por defecto (medium)
}
UIManager::~UIManager() {
@@ -51,16 +52,18 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
physical_window_width_ = physical_width;
physical_window_height_ = physical_height;
// Calcular tamaño de fuente apropiado según dimensiones físicas
current_font_size_ = calculateFontSize(physical_width, physical_height);
// Crear renderers de texto
text_renderer_ = new TextRenderer();
text_renderer_debug_ = new TextRenderer();
text_renderer_notifier_ = new TextRenderer();
// Inicializar renderers
// (el tamaño se configura dinámicamente en Engine según resolución)
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", 18, true);
// Inicializar renderers con tamaño dinámico
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
// Crear y configurar sistema de notificaciones
notifier_ = new Notifier();
@@ -69,7 +72,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
// Crear y configurar sistema de ayuda (overlay)
help_overlay_ = new HelpOverlay();
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height);
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, current_font_size_);
// Inicializar FPS counter
fps_last_time_ = SDL_GetTicks();
@@ -154,6 +157,32 @@ void UIManager::updateVSyncText(bool enabled) {
void UIManager::updatePhysicalWindowSize(int width, int height) {
physical_window_width_ = width;
physical_window_height_ = height;
// Calcular nuevo tamaño de fuente apropiado
int new_font_size = calculateFontSize(width, height);
// Si el tamaño cambió, reinicializar todos los text renderers
if (new_font_size != current_font_size_) {
current_font_size_ = new_font_size;
// Reinicializar text renderers con nuevo tamaño
if (text_renderer_) {
text_renderer_->reinitialize(current_font_size_);
}
if (text_renderer_debug_) {
text_renderer_debug_->reinitialize(current_font_size_);
}
if (text_renderer_notifier_) {
text_renderer_notifier_->reinitialize(current_font_size_);
}
// Reinicializar help overlay con nuevo tamaño de fuente
if (help_overlay_) {
help_overlay_->reinitializeFontSize(current_font_size_);
}
}
// Actualizar componentes de UI con nuevas dimensiones
notifier_->updateWindowSize(width, height);
if (help_overlay_) {
help_overlay_->updatePhysicalWindowSize(width, height);
@@ -376,3 +405,21 @@ std::string UIManager::gravityDirectionToString(int direction) const {
default: return "Desconocida";
}
}
int UIManager::calculateFontSize(int physical_width, int physical_height) const {
// Calcular área física de la ventana
int area = physical_width * physical_height;
// Stepped scaling con 3 tamaños:
// - SMALL: < 800x600 (480,000 pixels) → 14px
// - MEDIUM: 800x600 a 1920x1080 (2,073,600 pixels) → 18px
// - LARGE: > 1920x1080 → 24px
if (area < 480000) {
return 14; // Ventanas pequeñas
} else if (area < 2073600) {
return 18; // Ventanas medianas (default)
} else {
return 24; // Ventanas grandes
}
}

View File

@@ -166,6 +166,14 @@ class UIManager {
*/
std::string gravityDirectionToString(int direction) const;
/**
* @brief Calcula tamaño de fuente apropiado según dimensiones físicas
* @param physical_width Ancho físico de ventana
* @param physical_height Alto físico de ventana
* @return Tamaño de fuente (14px/18px/24px)
*/
int calculateFontSize(int physical_width, int physical_height) const;
// === Recursos de renderizado ===
TextRenderer* text_renderer_; // Texto obsoleto (DEPRECATED)
TextRenderer* text_renderer_debug_; // HUD de debug
@@ -194,4 +202,7 @@ class UIManager {
ThemeManager* theme_manager_; // Gestor de temas (para colores)
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
int physical_window_height_; // Alto físico de ventana (píxeles reales)
// === Sistema de escalado dinámico de texto ===
int current_font_size_; // Tamaño de fuente actual (14/18/24)
};