Implementación de deformación elástica con vértices para el logo: FADE IN (0.5s): - Scale: 120% → 100% con easing elástico (bounce) - Squash Y: 0.6 → 1.0 con easing back (aplastamiento) - Stretch X: compensación automática - Efecto: logo se "pega" aplastándose y rebotando FADE OUT (0.5s): - Scale: 100% → 120% (aceleración cuadrática) - Squash Y: 1.0 → 1.3 (estiramiento vertical) - Stretch X: 1.0 → 0.8 (compresión horizontal) - Rotación: 0° → ~5.7° (torsión sutil) - Efecto: logo se "despega" estirándose y girando Características técnicas: - Enum AppLogoAnimationType (ZOOM_ONLY / ELASTIC_STICK) - Renderizado con SDL_RenderGeometry para deformaciones - Funciones de easing: easeOutElastic() y easeOutBack() - Transformación de vértices con rotación y escala 2D - Actualmente fijo en ELASTIC_STICK para testing Limpieza adicional: - Eliminado dbgtxt.h (no utilizado) - Removidos SDL_Log de debug en HelpOverlay - Comentada variable no usada en ShapeManager 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
456 lines
16 KiB
C++
456 lines
16 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_;
|
|
}
|
|
|
|
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);
|
|
|
|
// 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) {
|
|
// 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;
|
|
}
|
|
|
|
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() {
|
|
// Calcular dimensiones necesarias según el texto
|
|
int text_width, text_height;
|
|
calculateTextDimensions(text_width, text_height);
|
|
|
|
// 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;
|
|
}
|
|
|
|
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<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 (AMBAS COLUMNAS)
|
|
// Verificar si la PRÓXIMA línea cabrá dentro del box con padding inferior
|
|
if (current_y + line_height >= box_height_ - padding) {
|
|
if (current_column == 0) {
|
|
// Columna 0 llena: cambiar a columna 1
|
|
current_column = 1;
|
|
current_x = padding + column1_width_ + padding;
|
|
current_y = content_start_y;
|
|
} else {
|
|
// Columna 1 llena: omitir resto de texto (no cabe)
|
|
// Preferible omitir que sobresalir del overlay
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
}
|