Add: Sistema de páginas para selección de temas + 5 nuevos temas

Implementación:
- 5 nuevos temas (2 estáticos: CRIMSON, EMERALD / 3 dinámicos: FIRE, AURORA, VOLCANIC)
- Sistema de páginas con Numpad Enter (Página 1 ↔ Página 2)
- Shift+B para ciclar temas hacia atrás
- Página 1: 9 temas estáticos + SUNRISE (Numpad 1-9, 0)
- Página 2: 5 temas dinámicos animados (Numpad 1-5)

Motivo:
- Shift+Numpad no funciona en Windows (limitación hardware/OS)
- Solución: Toggle de página con Numpad Enter

Archivos modificados:
- defines.h: Añadidos 5 nuevos ColorTheme enum values
- theme_manager.h: Añadido cyclePrevTheme() + actualizada doc 10→15 temas
- theme_manager.cpp: Implementados 5 nuevos temas + cyclePrevTheme()
- engine.h: Añadida variable theme_page_ (0 o 1)
- engine.cpp: Handlers Numpad Enter, KP_1-9,0 con sistema de páginas, SDLK_B con Shift detection
- CLAUDE.md: Documentación actualizada con tablas de 2 páginas

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-09 18:04:13 +02:00
parent f1bafc8a4f
commit c50ecbc02a
6 changed files with 380 additions and 96 deletions

View File

@@ -22,7 +22,7 @@
- ✅ Contador FPS en tiempo real (esquina superior derecha, amarillo) - ✅ Contador FPS en tiempo real (esquina superior derecha, amarillo)
- ✅ Control V-Sync dinámico con tecla "V" (ON/OFF) - ✅ Control V-Sync dinámico con tecla "V" (ON/OFF)
- ✅ Display V-Sync (esquina superior izquierda, cian) - ✅ Display V-Sync (esquina superior izquierda, cian)
-**Sistema de temas visuales** - 4 temas (SUNSET/OCEAN/NEON/FOREST) -**Sistema de temas visuales** - 15 temas (9 estáticos + 6 dinámicos con animación)
-**Batch rendering optimizado** - Maneja hasta 100,000 sprites -**Batch rendering optimizado** - Maneja hasta 100,000 sprites
#### 3. **NUEVA CARACTERÍSTICA: Gravedad Direccional** 🎯 #### 3. **NUEVA CARACTERÍSTICA: Gravedad Direccional** 🎯
@@ -63,13 +63,43 @@
| **C** | **🌐 MODO ROTOBALL - Toggle esfera 3D rotante** | | **C** | **🌐 MODO ROTOBALL - Toggle esfera 3D rotante** |
| V | Alternar V-Sync ON/OFF | | V | Alternar V-Sync ON/OFF |
| H | **Toggle debug display (FPS, V-Sync, física, gravedad, modo)** | | H | **Toggle debug display (FPS, V-Sync, física, gravedad, modo)** |
| Num 1-5 | Selección directa de tema (1-Atardecer/2-Océano/3-Neón/4-Bosque/5-RGB) | | **Numpad Enter** | **Toggle página de temas (Página 1 ↔ Página 2)** |
| T | Ciclar entre temas de colores | | **Numpad 1-9, 0** | **Acceso directo a temas según página activa** (ver tablas abajo) |
| B | Ciclar entre TODOS los temas de colores (15 temas) - Adelante |
| Shift+B | Ciclar entre TODOS los temas de colores - Atrás |
| 1-8 | Cambiar número de pelotas (1 a 100,000) | | 1-8 | Cambiar número de pelotas (1 a 100,000) |
| ESPACIO | Impulsar pelotas hacia arriba | | ESPACIO | Impulsar pelotas hacia arriba |
| G | Alternar gravedad ON/OFF (mantiene dirección) | | G | Alternar gravedad ON/OFF (mantiene dirección) |
| ESC | Salir | | ESC | Salir |
### 🎨 Temas de Colores (15 Temas Disponibles - Sistema de 2 Páginas)
**IMPORTANTE:** Usa **Numpad Enter** para cambiar entre Página 1 y Página 2
#### **Página 1** (Temas Estáticos + 1 Dinámico)
| Tecla | Tema | Tipo | Descripción |
|-------|------|------|-------------|
| Numpad 1 | ATARDECER | Estático | Naranjas, rojos, amarillos, rosas |
| Numpad 2 | OCÉANO | Estático | Azules, turquesas, blancos |
| Numpad 3 | NEÓN | Estático | Cian, magenta, verde lima, amarillo vibrante |
| Numpad 4 | BOSQUE | Estático | Verdes, marrones, amarillos otoño |
| Numpad 5 | RGB | Estático | Círculo cromático 24 colores (fondo blanco) |
| Numpad 6 | MONOCROMO | Estático | Fondo negro degradado, sprites blancos |
| Numpad 7 | LAVANDA | Estático | Degradado violeta-azul, pelotas amarillo dorado |
| Numpad 8 | CARMESÍ | Estático | Fondo negro-rojo, pelotas rojas uniformes |
| Numpad 9 | ESMERALDA | Estático | Fondo negro-verde, pelotas verdes uniformes |
| Numpad 0 | AMANECER | **Dinámico** | Noche → Alba → Día (loop 12s) |
#### **Página 2** (Temas Dinámicos Animados)
| Tecla | Tema | Tipo | Descripción |
|-------|------|------|-------------|
| Numpad 1 | OLAS OCEÁNICAS | **Dinámico** | Azul oscuro ↔ Turquesa (loop 8s) |
| Numpad 2 | PULSO NEÓN | **Dinámico** | Negro ↔ Neón brillante (ping-pong 3s) |
| Numpad 3 | FUEGO | **Dinámico** | Brasas → Llamas → Inferno (loop 10s) |
| Numpad 4 | AURORA | **Dinámico** | Verde → Violeta → Cian (loop 14s) |
| Numpad 5 | VOLCÁN | **Dinámico** | Ceniza → Erupción → Lava (loop 12s) |
| Numpad 6-9, 0 | (sin asignar) | - | Sin función en Página 2 |
### 🎯 Debug Display (Tecla H) ### 🎯 Debug Display (Tecla H)
Cuando está activado muestra: Cuando está activado muestra:

View File

@@ -84,7 +84,7 @@ enum class GravityDirection {
RIGHT // → Gravedad hacia la derecha RIGHT // → Gravedad hacia la derecha
}; };
// Enum para temas de colores (seleccionables con teclado numérico) // Enum para temas de colores (seleccionables con teclado numérico y Shift+Numpad)
// Todos los temas usan ahora sistema dinámico de keyframes // Todos los temas usan ahora sistema dinámico de keyframes
enum class ColorTheme { enum class ColorTheme {
SUNSET = 0, // Naranjas, rojos, amarillos, rosas (estático: 1 keyframe) SUNSET = 0, // Naranjas, rojos, amarillos, rosas (estático: 1 keyframe)
@@ -94,9 +94,14 @@ enum class ColorTheme {
RGB = 4, // RGB puros y subdivisiones matemáticas - fondo blanco (estático: 1 keyframe) RGB = 4, // RGB puros y subdivisiones matemáticas - fondo blanco (estático: 1 keyframe)
MONOCHROME = 5, // Fondo negro degradado, sprites blancos monocromáticos (estático: 1 keyframe) MONOCHROME = 5, // Fondo negro degradado, sprites blancos monocromáticos (estático: 1 keyframe)
LAVENDER = 6, // Degradado violeta-azul, pelotas amarillo dorado (estático: 1 keyframe) LAVENDER = 6, // Degradado violeta-azul, pelotas amarillo dorado (estático: 1 keyframe)
SUNRISE = 7, // Amanecer: Noche → Alba → Día (animado: 4 keyframes, 12s ciclo) CRIMSON = 7, // Fondo negro-rojo, pelotas rojas uniformes (estático: 1 keyframe)
OCEAN_WAVES = 8, // Olas oceánicas: Azul oscuro ↔ Turquesa (animado: 3 keyframes, 8s ciclo) EMERALD = 8, // Fondo negro-verde, pelotas verdes uniformes (estático: 1 keyframe)
NEON_PULSE = 9 // Pulso neón: Negro ↔ Neón vibrante (animado: 3 keyframes, 3s ping-pong) SUNRISE = 9, // Amanecer: Noche → Alba → Día (animado: 4 keyframes, 12s ciclo)
OCEAN_WAVES = 10, // Olas oceánicas: Azul oscuro ↔ Turquesa (animado: 3 keyframes, 8s ciclo)
NEON_PULSE = 11, // Pulso neón: Negro ↔ Neón vibrante (animado: 3 keyframes, 3s ping-pong)
FIRE = 12, // Fuego vivo: Brasas → Llamas → Inferno (animado: 4 keyframes, 10s ciclo)
AURORA = 13, // Aurora boreal: Verde → Violeta → Cian (animado: 4 keyframes, 14s ciclo)
VOLCANIC = 14 // Erupción volcánica: Ceniza → Erupción → Lava (animado: 4 keyframes, 12s ciclo)
}; };
// Enum para tipo de figura 3D // Enum para tipo de figura 3D

View File

@@ -421,114 +421,171 @@ void Engine::handleEvents() {
// Ciclar temas de color (movido de T a B) // Ciclar temas de color (movido de T a B)
case SDLK_B: case SDLK_B:
// Ciclar al siguiente tema con transición suave {
theme_manager_->cycleTheme(); // Detectar si Shift está presionado
SDL_Keymod modstate = SDL_GetModState();
if (modstate & SDL_KMOD_SHIFT) {
// Shift+B: Ciclar hacia atrás (tema anterior)
theme_manager_->cyclePrevTheme();
} else {
// B solo: Ciclar hacia adelante (tema siguiente)
theme_manager_->cycleTheme();
}
// Mostrar nombre del tema (solo si NO estamos en modo demo) // Mostrar nombre del tema (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) { if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true; show_text_ = true;
text_init_time_ = SDL_GetTicks(); text_init_time_ = SDL_GetTicks();
}
} }
break; break;
// Temas de colores con teclado numérico (con transición suave) // Temas de colores con teclado numérico (con transición suave)
case SDLK_KP_1: case SDLK_KP_1:
theme_manager_->switchToTheme(0); // SUNSET // Página 0: SUNSET (0), Página 1: OCEAN_WAVES (10)
if (current_app_mode_ == AppMode::MANUAL) { {
text_ = theme_manager_->getCurrentThemeNameES(); int theme_index = (theme_page_ == 0) ? 0 : 10;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; theme_manager_->switchToTheme(theme_index);
show_text_ = true; if (current_app_mode_ == AppMode::MANUAL) {
text_init_time_ = SDL_GetTicks(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_2: case SDLK_KP_2:
theme_manager_->switchToTheme(1); // OCEAN // Página 0: OCEAN (1), Página 1: NEON_PULSE (11)
if (current_app_mode_ == AppMode::MANUAL) { {
text_ = theme_manager_->getCurrentThemeNameES(); int theme_index = (theme_page_ == 0) ? 1 : 11;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; theme_manager_->switchToTheme(theme_index);
show_text_ = true; if (current_app_mode_ == AppMode::MANUAL) {
text_init_time_ = SDL_GetTicks(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_3: case SDLK_KP_3:
theme_manager_->switchToTheme(2); // NEON // Página 0: NEON (2), Página 1: FIRE (12)
if (current_app_mode_ == AppMode::MANUAL) { {
text_ = theme_manager_->getCurrentThemeNameES(); int theme_index = (theme_page_ == 0) ? 2 : 12;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; theme_manager_->switchToTheme(theme_index);
show_text_ = true; if (current_app_mode_ == AppMode::MANUAL) {
text_init_time_ = SDL_GetTicks(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_4: case SDLK_KP_4:
theme_manager_->switchToTheme(3); // FOREST // Página 0: FOREST (3), Página 1: AURORA (13)
if (current_app_mode_ == AppMode::MANUAL) { {
text_ = theme_manager_->getCurrentThemeNameES(); int theme_index = (theme_page_ == 0) ? 3 : 13;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; theme_manager_->switchToTheme(theme_index);
show_text_ = true; if (current_app_mode_ == AppMode::MANUAL) {
text_init_time_ = SDL_GetTicks(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_5: case SDLK_KP_5:
theme_manager_->switchToTheme(4); // RGB // Página 0: RGB (4), Página 1: VOLCANIC (14)
if (current_app_mode_ == AppMode::MANUAL) { {
text_ = theme_manager_->getCurrentThemeNameES(); int theme_index = (theme_page_ == 0) ? 4 : 14;
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; theme_manager_->switchToTheme(theme_index);
show_text_ = true; if (current_app_mode_ == AppMode::MANUAL) {
text_init_time_ = SDL_GetTicks(); text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_6: case SDLK_KP_6:
theme_manager_->switchToTheme(5); // MONOCHROME // Solo página 0: MONOCHROME (5)
if (current_app_mode_ == AppMode::MANUAL) { if (theme_page_ == 0) {
text_ = theme_manager_->getCurrentThemeNameES(); theme_manager_->switchToTheme(5); // MONOCHROME
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; if (current_app_mode_ == AppMode::MANUAL) {
show_text_ = true; text_ = theme_manager_->getCurrentThemeNameES();
text_init_time_ = SDL_GetTicks(); text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_7: case SDLK_KP_7:
theme_manager_->switchToTheme(6); // LAVENDER // Solo página 0: LAVENDER (6)
if (current_app_mode_ == AppMode::MANUAL) { if (theme_page_ == 0) {
text_ = theme_manager_->getCurrentThemeNameES(); theme_manager_->switchToTheme(6); // LAVENDER
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; if (current_app_mode_ == AppMode::MANUAL) {
show_text_ = true; text_ = theme_manager_->getCurrentThemeNameES();
text_init_time_ = SDL_GetTicks(); text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
// Temas dinámicos (animados) - Solo Numpad 8/9/0 (teclas normales usadas para escenarios)
case SDLK_KP_8: case SDLK_KP_8:
theme_manager_->switchToTheme(7); // SUNRISE // Solo página 0: CRIMSON (7)
if (current_app_mode_ == AppMode::MANUAL) { if (theme_page_ == 0) {
text_ = theme_manager_->getCurrentThemeNameES(); theme_manager_->switchToTheme(7); // CRIMSON
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; if (current_app_mode_ == AppMode::MANUAL) {
show_text_ = true; text_ = theme_manager_->getCurrentThemeNameES();
text_init_time_ = SDL_GetTicks(); text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_9: case SDLK_KP_9:
theme_manager_->switchToTheme(8); // OCEAN WAVES // Solo página 0: EMERALD (8)
if (current_app_mode_ == AppMode::MANUAL) { if (theme_page_ == 0) {
text_ = theme_manager_->getCurrentThemeNameES(); theme_manager_->switchToTheme(8); // EMERALD
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; if (current_app_mode_ == AppMode::MANUAL) {
show_text_ = true; text_ = theme_manager_->getCurrentThemeNameES();
text_init_time_ = SDL_GetTicks(); text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
} }
break; break;
case SDLK_KP_0: case SDLK_KP_0:
theme_manager_->switchToTheme(9); // NEON PULSE // Solo página 0: SUNRISE (9)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(9); // SUNRISE
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
}
break;
// Toggle de página de temas (Numpad Enter)
case SDLK_KP_ENTER:
// Alternar entre página 0 y página 1
theme_page_ = (theme_page_ == 0) ? 1 : 0;
// Mostrar feedback visual (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) { if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES(); text_ = (theme_page_ == 0) ? "PAGINA 1" : "PAGINA 2";
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2; text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true; show_text_ = true;
text_init_time_ = SDL_GetTicks(); text_init_time_ = SDL_GetTicks();
@@ -1627,8 +1684,8 @@ void Engine::performDemoAction(bool is_lite) {
// Cambiar tema (15%) // Cambiar tema (15%)
accumulated_weight += DEMO_WEIGHT_THEME; accumulated_weight += DEMO_WEIGHT_THEME;
if (random_value < accumulated_weight) { if (random_value < accumulated_weight) {
// Elegir entre TODOS los 10 temas (estáticos + dinámicos) // Elegir entre TODOS los 15 temas (9 estáticos + 6 dinámicos)
int random_theme_index = rand() % 10; int random_theme_index = rand() % 15;
theme_manager_->switchToTheme(random_theme_index); theme_manager_->switchToTheme(random_theme_index);
return; return;
} }
@@ -1717,8 +1774,8 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
scenario_ = valid_scenarios[rand() % 5]; scenario_ = valid_scenarios[rand() % 5];
initBalls(scenario_); initBalls(scenario_);
// 2. Tema (elegir entre TODOS los 10 temas) // 2. Tema (elegir entre TODOS los 15 temas)
int random_theme_index = rand() % 10; int random_theme_index = rand() % 15;
theme_manager_->switchToTheme(random_theme_index); theme_manager_->switchToTheme(random_theme_index);
// 3. Sprite // 3. Sprite

View File

@@ -82,6 +82,7 @@ class Engine {
// Sistema de temas (delegado a ThemeManager) // Sistema de temas (delegado a ThemeManager)
std::unique_ptr<ThemeManager> theme_manager_; std::unique_ptr<ThemeManager> theme_manager_;
int theme_page_ = 0; // Página actual de temas (0 o 1) para acceso por Numpad
// Sistema de Figuras 3D (polimórfico) // Sistema de Figuras 3D (polimórfico)
SimulationMode current_mode_ = SimulationMode::PHYSICS; SimulationMode current_mode_ = SimulationMode::PHYSICS;

View File

@@ -9,10 +9,10 @@
void ThemeManager::initialize() { void ThemeManager::initialize() {
themes_.clear(); themes_.clear();
themes_.reserve(10); // 7 estáticos + 3 dinámicos themes_.reserve(15); // 9 estáticos + 6 dinámicos
// ======================================== // ========================================
// TEMAS ESTÁTICOS (índices 0-6) // TEMAS ESTÁTICOS (índices 0-8)
// ======================================== // ========================================
// 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas // 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas
@@ -128,11 +128,37 @@ void ThemeManager::initialize() {
} }
)); ));
// 7: CRIMSON (Carmesí) - Fondo negro-rojo oscuro, pelotas rojas uniformes
themes_.push_back(std::make_unique<StaticTheme>(
"CRIMSON",
"CARMESI",
255, 100, 100, // Color texto: rojo claro
40.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: rojo muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
std::vector<Color>{
{220, 20, 60}, {220, 20, 60}, {220, 20, 60}, {220, 20, 60},
{220, 20, 60}, {220, 20, 60}, {220, 20, 60}, {220, 20, 60}
}
));
// 8: EMERALD (Esmeralda) - Fondo negro-verde oscuro, pelotas verdes uniformes
themes_.push_back(std::make_unique<StaticTheme>(
"EMERALD",
"ESMERALDA",
100, 255, 100, // Color texto: verde claro
0.0f / 255.0f, 40.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: verde muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
std::vector<Color>{
{50, 205, 50}, {50, 205, 50}, {50, 205, 50}, {50, 205, 50},
{50, 205, 50}, {50, 205, 50}, {50, 205, 50}, {50, 205, 50}
}
));
// ======================================== // ========================================
// TEMAS DINÁMICOS (índices 7-9) // TEMAS DINÁMICOS (índices 9-14)
// ======================================== // ========================================
// 7: SUNRISE (Amanecer) - Noche → Alba → Día (loop) // 9: SUNRISE (Amanecer) - Noche → Alba → Día (loop)
themes_.push_back(std::make_unique<DynamicTheme>( themes_.push_back(std::make_unique<DynamicTheme>(
"SUNRISE", "SUNRISE",
"AMANECER", "AMANECER",
@@ -173,7 +199,7 @@ void ThemeManager::initialize() {
true // Loop = true true // Loop = true
)); ));
// 8: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop) // 10: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop)
themes_.push_back(std::make_unique<DynamicTheme>( themes_.push_back(std::make_unique<DynamicTheme>(
"OCEAN WAVES", "OCEAN WAVES",
"OLAS OCEANICAS", "OLAS OCEANICAS",
@@ -204,7 +230,7 @@ void ThemeManager::initialize() {
true // Loop = true true // Loop = true
)); ));
// 9: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong) // 11: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong)
themes_.push_back(std::make_unique<DynamicTheme>( themes_.push_back(std::make_unique<DynamicTheme>(
"NEON PULSE", "NEON PULSE",
"PULSO NEON", "PULSO NEON",
@@ -234,6 +260,159 @@ void ThemeManager::initialize() {
}, },
true // Loop = true true // Loop = true
)); ));
// 12: FIRE (Fuego Vivo) - Brasas → Llamas → Inferno (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"FIRE",
"FUEGO",
255, 150, 80, // Color texto: naranja cálido
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Brasas oscuras (estado inicial)
{
60.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo superior: rojo muy oscuro
20.0f / 255.0f, 10.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: casi negro
std::vector<Color>{
{120, 40, 20}, {140, 35, 15}, {130, 38, 18}, {125, 42, 22},
{135, 37, 16}, {128, 40, 20}, {132, 39, 19}, {138, 36, 17}
},
0.0f // Estado inicial
},
// Keyframe 1: Llamas naranjas (transición)
{
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
std::vector<Color>{
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
},
3.5f // 3.5 segundos para llegar
},
// Keyframe 2: Inferno brillante (clímax)
{
255.0f / 255.0f, 180.0f / 255.0f, 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante
220.0f / 255.0f, 100.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: naranja intenso
std::vector<Color>{
{255, 220, 100}, {255, 200, 80}, {255, 240, 120}, {255, 210, 90},
{255, 230, 110}, {255, 205, 85}, {255, 225, 105}, {255, 215, 95}
},
3.0f // 3 segundos para llegar
},
// Keyframe 3: Vuelta a llamas (antes de reiniciar loop)
{
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
std::vector<Color>{
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
},
3.5f // 3.5 segundos para volver
}
// Loop = true hará transición automática de keyframe 3 → keyframe 0
},
true // Loop = true (ciclo completo: brasas→llamas→inferno→llamas→brasas...)
));
// 13: AURORA (Aurora Boreal) - Verde → Violeta → Cian (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"AURORA",
"AURORA",
150, 255, 200, // Color texto: verde claro
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Verde aurora (estado inicial)
{
30.0f / 255.0f, 80.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: verde oscuro
10.0f / 255.0f, 20.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
std::vector<Color>{
{100, 255, 180}, {80, 240, 160}, {120, 255, 200}, {90, 245, 170},
{110, 255, 190}, {85, 242, 165}, {105, 252, 185}, {95, 248, 175}
},
0.0f // Estado inicial
},
// Keyframe 1: Violeta aurora (transición)
{
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
std::vector<Color>{
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
},
5.0f // 5 segundos para llegar (transición lenta)
},
// Keyframe 2: Cian aurora (clímax)
{
60.0f / 255.0f, 180.0f / 255.0f, 220.0f / 255.0f, // Fondo superior: cian brillante
20.0f / 255.0f, 80.0f / 255.0f, 120.0f / 255.0f, // Fondo inferior: azul oscuro
std::vector<Color>{
{100, 220, 255}, {80, 200, 240}, {120, 240, 255}, {90, 210, 245},
{110, 230, 255}, {85, 205, 242}, {105, 225, 252}, {95, 215, 248}
},
4.5f // 4.5 segundos para llegar
},
// Keyframe 3: Vuelta a violeta (antes de reiniciar)
{
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
std::vector<Color>{
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
},
4.5f // 4.5 segundos para volver
}
// Loop = true hará transición automática de keyframe 3 → keyframe 0
},
true // Loop = true (ciclo: verde→violeta→cian→violeta→verde...)
));
// 14: VOLCANIC (Erupción Volcánica) - Ceniza → Erupción → Lava (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"VOLCANIC",
"VOLCAN",
200, 120, 80, // Color texto: naranja apagado
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Ceniza oscura (pre-erupción)
{
40.0f / 255.0f, 40.0f / 255.0f, 45.0f / 255.0f, // Fondo superior: gris oscuro
20.0f / 255.0f, 15.0f / 255.0f, 15.0f / 255.0f, // Fondo inferior: casi negro
std::vector<Color>{
{80, 80, 90}, {75, 75, 85}, {85, 85, 95}, {78, 78, 88},
{82, 82, 92}, {76, 76, 86}, {84, 84, 94}, {79, 79, 89}
},
0.0f // Estado inicial
},
// Keyframe 1: Erupción naranja-roja (explosión)
{
180.0f / 255.0f, 60.0f / 255.0f, 30.0f / 255.0f, // Fondo superior: naranja-rojo
80.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
std::vector<Color>{
{255, 80, 40}, {255, 100, 50}, {255, 70, 35}, {255, 90, 45},
{255, 75, 38}, {255, 95, 48}, {255, 85, 42}, {255, 78, 40}
},
3.0f // 3 segundos para erupción (rápido)
},
// Keyframe 2: Lava brillante (clímax)
{
220.0f / 255.0f, 120.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: naranja brillante
180.0f / 255.0f, 60.0f / 255.0f, 20.0f / 255.0f, // Fondo inferior: naranja-rojo
std::vector<Color>{
{255, 180, 80}, {255, 200, 100}, {255, 170, 70}, {255, 190, 90},
{255, 175, 75}, {255, 195, 95}, {255, 185, 85}, {255, 178, 78}
},
3.5f // 3.5 segundos para lava máxima
},
// Keyframe 3: Enfriamiento (vuelta a ceniza gradual)
{
100.0f / 255.0f, 80.0f / 255.0f, 70.0f / 255.0f, // Fondo superior: gris-naranja
50.0f / 255.0f, 40.0f / 255.0f, 35.0f / 255.0f, // Fondo inferior: gris oscuro
std::vector<Color>{
{150, 120, 100}, {140, 110, 90}, {160, 130, 110}, {145, 115, 95},
{155, 125, 105}, {142, 112, 92}, {158, 128, 108}, {148, 118, 98}
},
5.5f // 5.5 segundos para enfriamiento (lento)
}
// Loop = true hará transición automática de keyframe 3 → keyframe 0
},
true // Loop = true (ciclo: ceniza→erupción→lava→enfriamiento→ceniza...)
));
} }
// ============================================================================ // ============================================================================
@@ -296,6 +475,17 @@ void ThemeManager::cycleTheme() {
switchToTheme(next_theme_index); switchToTheme(next_theme_index);
} }
void ThemeManager::cyclePrevTheme() {
// Calcular tema anterior con wraparound (14→13→...→1→0→14)
int prev_theme_index = (current_theme_index_ - 1);
if (prev_theme_index < 0) {
prev_theme_index = static_cast<int>(themes_.size()) - 1; // Wrap to último tema
}
// Usar switchToTheme() para obtener transición LERP automáticamente
switchToTheme(prev_theme_index);
}
void ThemeManager::pauseDynamic() { void ThemeManager::pauseDynamic() {
// Solo funciona si el tema actual es dinámico // Solo funciona si el tema actual es dinámico
if (themes_[current_theme_index_]->needsUpdate()) { if (themes_[current_theme_index_]->needsUpdate()) {

View File

@@ -12,9 +12,9 @@
* ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos) * ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos)
* *
* PHASE 2 - Sistema Unificado: * PHASE 2 - Sistema Unificado:
* - Vector unificado de 10 temas (7 estáticos + 3 dinámicos) * - Vector unificado de 15 temas (9 estáticos + 6 dinámicos)
* - Índices 0-9 mapeados a ColorTheme enum (SUNSET=0, OCEAN=1, ..., NEON_PULSE=9) * - Índices 0-14 mapeados a ColorTheme enum (SUNSET=0, OCEAN=1, ..., VOLCANIC=14)
* - API simplificada: switchToTheme(0-9) para cualquier tema * - API simplificada: switchToTheme(0-14) para cualquier tema
* - Sin lógica dual (eliminados if(dynamic_theme_active_) scattered) * - Sin lógica dual (eliminados if(dynamic_theme_active_) scattered)
* *
* PHASE 3 - LERP Universal: * PHASE 3 - LERP Universal:
@@ -24,7 +24,7 @@
* - Duración configurable (THEME_TRANSITION_DURATION = 0.5s por defecto) * - Duración configurable (THEME_TRANSITION_DURATION = 0.5s por defecto)
* *
* Responsabilidades: * Responsabilidades:
* - Mantener 10 temas polimórficos (StaticTheme / DynamicTheme) * - Mantener 15 temas polimórficos (StaticTheme / DynamicTheme)
* - Actualizar animación de tema activo si es dinámico * - Actualizar animación de tema activo si es dinámico
* - Gestionar transiciones LERP suaves entre temas * - Gestionar transiciones LERP suaves entre temas
* - Proporcionar colores interpolados para renderizado (con LERP si hay transición activa) * - Proporcionar colores interpolados para renderizado (con LERP si hay transición activa)
@@ -32,7 +32,7 @@
* Uso desde Engine: * Uso desde Engine:
* - initialize() al inicio * - initialize() al inicio
* - update(delta_time) cada frame (actualiza tema activo + transición LERP) * - update(delta_time) cada frame (actualiza tema activo + transición LERP)
* - switchToTheme(0-9) para cambiar tema con transición suave (Numpad 1-0, Tecla B) * - switchToTheme(0-14) para cambiar tema con transición suave (Numpad/Shift+Numpad, Tecla B)
* - getInterpolatedColor(index) en render loop (retorna color con LERP si transitioning) * - getInterpolatedColor(index) en render loop (retorna color con LERP si transitioning)
*/ */
class ThemeManager { class ThemeManager {
@@ -42,12 +42,13 @@ class ThemeManager {
~ThemeManager() = default; ~ThemeManager() = default;
// Inicialización // Inicialización
void initialize(); // Inicializa 10 temas unificados (7 estáticos + 3 dinámicos) void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos)
// Interfaz unificada (PHASE 2 + PHASE 3) // Interfaz unificada (PHASE 2 + PHASE 3)
void switchToTheme(int theme_index); // Cambia a tema 0-9 con transición LERP suave (PHASE 3) void switchToTheme(int theme_index); // Cambia a tema 0-14 con transición LERP suave (PHASE 3)
void update(float delta_time); // Actualiza transición LERP + tema activo si es dinámico void update(float delta_time); // Actualiza transición LERP + tema activo si es dinámico
void cycleTheme(); // Cicla al siguiente tema (0→1→...→9→0) - Tecla B void cycleTheme(); // Cicla al siguiente tema (0→1→...→14→0) - Tecla B
void cyclePrevTheme(); // Cicla al tema anterior (14→...→1→0) - Shift+B
void pauseDynamic(); // Toggle pausa de animación (Shift+D, solo dinámicos) void pauseDynamic(); // Toggle pausa de animación (Shift+D, solo dinámicos)
// Queries de colores (usado en rendering) // Queries de colores (usado en rendering)
@@ -73,12 +74,12 @@ class ThemeManager {
// DATOS UNIFICADOS (PHASE 2) // DATOS UNIFICADOS (PHASE 2)
// ======================================== // ========================================
// Vector unificado de 10 temas (índices 0-9) // Vector unificado de 15 temas (índices 0-14)
// 0-6: Estáticos (SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER) // 0-8: Estáticos (SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER, CRIMSON, EMERALD)
// 7-9: Dinámicos (SUNRISE, OCEAN_WAVES, NEON_PULSE) // 9-14: Dinámicos (SUNRISE, OCEAN_WAVES, NEON_PULSE, FIRE, AURORA, VOLCANIC)
std::vector<std::unique_ptr<Theme>> themes_; std::vector<std::unique_ptr<Theme>> themes_;
// Índice de tema activo actual (0-9) // Índice de tema activo actual (0-14)
int current_theme_index_ = 0; // Por defecto SUNSET int current_theme_index_ = 0; // Por defecto SUNSET
// ======================================== // ========================================
@@ -102,8 +103,8 @@ class ThemeManager {
// ======================================== // ========================================
// Inicialización // Inicialización
void initializeStaticThemes(); // Crea 7 temas estáticos (índices 0-6) void initializeStaticThemes(); // Crea 9 temas estáticos (índices 0-8)
void initializeDynamicThemes(); // Crea 3 temas dinámicos (índices 7-9) void initializeDynamicThemes(); // Crea 6 temas dinámicos (índices 9-14)
// Sistema de transición LERP (PHASE 3) // Sistema de transición LERP (PHASE 3)
std::unique_ptr<ThemeSnapshot> captureCurrentSnapshot() const; // Captura snapshot del tema actual std::unique_ptr<ThemeSnapshot> captureCurrentSnapshot() const; // Captura snapshot del tema actual