Files
vibe3_physics/source/theme_manager.cpp
Sergio Valor 0d1608712b Add: Sistema de notificaciones con colores de fondo temáticos
CARACTERÍSTICAS:
- Notificaciones con fondo personalizado por tema (15 temas)
- Soporte completo para temas estáticos y dinámicos
- Interpolación LERP de colores durante transiciones
- Actualización por frame durante animaciones de temas

IMPLEMENTACIÓN:

Theme System:
- Añadido getNotificationBackgroundColor() a interfaz Theme
- StaticTheme: Color fijo por tema
- DynamicTheme: Interpolación entre keyframes
- ThemeManager: LERP durante transiciones (PHASE 3)
- ThemeSnapshot: Captura color para transiciones suaves

Colores por Tema:
Estáticos (9):
  - SUNSET: Púrpura oscuro (120, 40, 80)
  - OCEAN: Azul marino (20, 50, 90)
  - NEON: Púrpura oscuro (60, 0, 80)
  - FOREST: Marrón tierra (70, 50, 30)
  - RGB: Gris claro (220, 220, 220)
  - MONOCHROME: Gris oscuro (50, 50, 50)
  - LAVENDER: Violeta oscuro (80, 50, 100)
  - CRIMSON: Rojo oscuro (80, 10, 10)
  - EMERALD: Verde oscuro (10, 80, 10)

Dinámicos (6, 20 keyframes totales):
  - SUNRISE: 3 keyframes (noche→alba→día)
  - OCEAN_WAVES: 2 keyframes (profundo→claro)
  - NEON_PULSE: 2 keyframes (apagado→encendido)
  - FIRE: 4 keyframes (brasas→llamas→inferno→llamas)
  - AURORA: 4 keyframes (verde→violeta→cian→violeta)
  - VOLCANIC: 4 keyframes (ceniza→erupción→lava→enfriamiento)

Notifier:
- Añadido SDL_Color bg_color a estructura Notification
- Método show() acepta parámetro bg_color
- renderBackground() usa color dinámico (no negro fijo)
- Soporte para cambios de color cada frame

Engine:
- Obtiene color de fondo desde ThemeManager
- Pasa bg_color al notifier en cada notificación
- Sincronizado con tema activo y transiciones

FIXES:
- TEXT_ABSOLUTE_SIZE cambiado de 16px a 12px (múltiplo nativo)
- Centrado de notificaciones corregido en F3 fullscreen
- updatePhysicalWindowSize() usa SDL_GetCurrentDisplayMode en F3
- Notificaciones centradas correctamente en ventana/F3/F4

🎨 Generated with Claude Code
2025-10-10 07:17:06 +02:00

654 lines
31 KiB
C++

#include "theme_manager.h"
#include "themes/static_theme.h"
#include "themes/dynamic_theme.h"
// ============================================================================
// INICIALIZACIÓN
// ============================================================================
void ThemeManager::initialize() {
themes_.clear();
themes_.reserve(15); // 9 estáticos + 6 dinámicos
// ========================================
// TEMAS ESTÁTICOS (índices 0-8)
// ========================================
// 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas
themes_.push_back(std::make_unique<StaticTheme>(
"Sunset",
"Atardecer",
255, 140, 60, // Color texto: naranja cálido
120, 40, 80, // Color fondo notificación: púrpura oscuro (contrasta con naranja)
180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: naranja suave
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: púrpura oscuro
std::vector<Color>{
{255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147},
{255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60}
}
));
// 1: OCEAN (Océano) - Azules, turquesas, blancos
themes_.push_back(std::make_unique<StaticTheme>(
"Ocean",
"Océano",
80, 200, 255, // Color texto: azul océano
20, 50, 90, // Color fondo notificación: azul marino oscuro (contrasta con cian)
100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior: azul cielo
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: azul marino
std::vector<Color>{
{0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230},
{70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208}
}
));
// 2: NEON - Cian, magenta, verde lima, amarillo vibrante
themes_.push_back(std::make_unique<StaticTheme>(
"Neon",
"Neón",
255, 60, 255, // Color texto: magenta brillante
60, 0, 80, // Color fondo notificación: púrpura muy oscuro (contrasta con neón)
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: negro azulado
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
std::vector<Color>{
{0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0},
{255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0}
}
));
// 3: FOREST (Bosque) - Verdes, marrones, amarillos otoño
themes_.push_back(std::make_unique<StaticTheme>(
"Forest",
"Bosque",
100, 255, 100, // Color texto: verde natural
70, 50, 30, // Color fondo notificación: marrón oscuro tierra (contrasta con verde)
144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior: verde claro
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior: marrón tierra
std::vector<Color>{
{34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0},
{210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50}
}
));
// 4: RGB - Círculo cromático con 24 puntos (cada 15°)
themes_.push_back(std::make_unique<StaticTheme>(
"RGB",
"RGB",
100, 100, 100, // Color texto: gris oscuro
220, 220, 220, // Color fondo notificación: gris muy claro (contrasta sobre blanco)
1.0f, 1.0f, 1.0f, // Fondo superior: blanco puro
1.0f, 1.0f, 1.0f, // Fondo inferior: blanco puro (sin degradado)
std::vector<Color>{
{255, 0, 0}, // 0° - Rojo puro
{255, 64, 0}, // 15° - Rojo-Naranja
{255, 128, 0}, // 30° - Naranja
{255, 191, 0}, // 45° - Naranja-Amarillo
{255, 255, 0}, // 60° - Amarillo puro
{191, 255, 0}, // 75° - Amarillo-Verde claro
{128, 255, 0}, // 90° - Verde-Amarillo
{64, 255, 0}, // 105° - Verde claro-Amarillo
{0, 255, 0}, // 120° - Verde puro
{0, 255, 64}, // 135° - Verde-Cian claro
{0, 255, 128}, // 150° - Verde-Cian
{0, 255, 191}, // 165° - Verde claro-Cian
{0, 255, 255}, // 180° - Cian puro
{0, 191, 255}, // 195° - Cian-Azul claro
{0, 128, 255}, // 210° - Azul-Cian
{0, 64, 255}, // 225° - Azul claro-Cian
{0, 0, 255}, // 240° - Azul puro
{64, 0, 255}, // 255° - Azul-Magenta claro
{128, 0, 255}, // 270° - Azul-Magenta
{191, 0, 255}, // 285° - Azul claro-Magenta
{255, 0, 255}, // 300° - Magenta puro
{255, 0, 191}, // 315° - Magenta-Rojo claro
{255, 0, 128}, // 330° - Magenta-Rojo
{255, 0, 64} // 345° - Magenta claro-Rojo
}
));
// 5: MONOCHROME (Monocromo) - Fondo negro degradado, sprites blancos
themes_.push_back(std::make_unique<StaticTheme>(
"Monochrome",
"Monocromo",
200, 200, 200, // Color texto: gris claro
50, 50, 50, // Color fondo notificación: gris medio oscuro (contrasta con texto claro)
20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: gris muy oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
std::vector<Color>{
{255, 255, 255}, {255, 255, 255}, {255, 255, 255}, {255, 255, 255},
{255, 255, 255}, {255, 255, 255}, {255, 255, 255}, {255, 255, 255}
}
));
// 6: LAVENDER (Lavanda) - Degradado violeta oscuro → azul medianoche, pelotas amarillo dorado
themes_.push_back(std::make_unique<StaticTheme>(
"Lavender",
"Lavanda",
255, 200, 100, // Color texto: amarillo cálido
80, 50, 100, // Color fondo notificación: violeta muy oscuro (contrasta con amarillo)
120.0f / 255.0f, 80.0f / 255.0f, 140.0f / 255.0f, // Fondo superior: violeta oscuro
25.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul medianoche
std::vector<Color>{
{255, 215, 0}, {255, 215, 0}, {255, 215, 0}, {255, 215, 0},
{255, 215, 0}, {255, 215, 0}, {255, 215, 0}, {255, 215, 0}
}
));
// 7: CRIMSON (Carmesí) - Fondo negro-rojo oscuro, pelotas rojas uniformes
themes_.push_back(std::make_unique<StaticTheme>(
"Crimson",
"Carmesí",
255, 100, 100, // Color texto: rojo claro
80, 10, 10, // Color fondo notificación: rojo muy oscuro (contrasta con texto 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
10, 80, 10, // Color fondo notificación: verde muy oscuro (contrasta con texto 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 9-14)
// ========================================
// 9: SUNRISE (Amanecer) - Noche → Alba → Día (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"Sunrise",
"Amanecer",
255, 200, 100, // Color texto: amarillo cálido
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Noche oscura (estado inicial)
{
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
20, 30, 80, // Color fondo notificación: azul oscuro (noche)
std::vector<Color>{
{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}
},
0.0f // Sin transición (estado inicial)
},
// Keyframe 1: Alba naranja-rosa
{
180.0f / 255.0f, 100.0f / 255.0f, 120.0f / 255.0f, // Fondo superior: naranja-rosa
255.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo inferior: naranja cálido
140, 60, 80, // Color fondo notificación: naranja-rojo oscuro (alba)
std::vector<Color>{
{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
{255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}
},
4.0f // 4 segundos para llegar aquí
},
// Keyframe 2: Día brillante amarillo
{
255.0f / 255.0f, 240.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: amarillo claro
255.0f / 255.0f, 255.0f / 255.0f, 220.0f / 255.0f, // Fondo inferior: amarillo muy claro
200, 180, 140, // Color fondo notificación: amarillo oscuro (día)
std::vector<Color>{
{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
{255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}
},
3.0f // 3 segundos para llegar aquí
}
// NOTA: Keyframe 3 (vuelta a noche) eliminado - loop=true lo maneja automáticamente
},
true // Loop = true
));
// 10: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop)
themes_.push_back(std::make_unique<DynamicTheme>(
"Ocean Waves",
"Olas Oceánicas",
100, 220, 255, // Color texto: cian claro
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Profundidad oceánica (azul oscuro)
{
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
10, 30, 70, // Color fondo notificación: azul muy oscuro (profundidad)
std::vector<Color>{
{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}
},
0.0f // Estado inicial
},
// Keyframe 1: Aguas poco profundas (turquesa brillante)
{
100.0f / 255.0f, 200.0f / 255.0f, 230.0f / 255.0f, // Fondo superior: turquesa claro
50.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo inferior: turquesa medio
30, 100, 140, // Color fondo notificación: turquesa oscuro (aguas poco profundas)
std::vector<Color>{
{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
{105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}
},
4.0f // 4 segundos para llegar
}
// NOTA: Keyframe 2 (vuelta a profundidad) eliminado - loop=true lo maneja automáticamente
},
true // Loop = true
));
// 11: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong)
themes_.push_back(std::make_unique<DynamicTheme>(
"Neon Pulse",
"Pulso Neón",
255, 60, 255, // Color texto: magenta brillante
std::vector<DynamicThemeKeyframe>{
// Keyframe 0: Apagado (negro)
{
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: negro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
30, 30, 30, // Color fondo notificación: gris muy oscuro (apagado)
std::vector<Color>{
{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}
},
0.0f // Estado inicial
},
// Keyframe 1: Encendido (neón cian-magenta)
{
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: azul oscuro
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
60, 0, 80, // Color fondo notificación: púrpura oscuro (neón encendido)
std::vector<Color>{
{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}
},
1.5f // 1.5 segundos para encender (rápido)
}
// NOTA: Keyframe 2 (vuelta a apagado) eliminado - loop=true crea ping-pong automáticamente
},
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
70, 20, 10, // Color fondo notificación: rojo muy oscuro (brasas)
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
110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas)
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
160, 80, 30, // Color fondo notificación: naranja oscuro (inferno)
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
110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas)
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
15, 50, 40, // Color fondo notificación: verde muy oscuro (aurora verde)
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
70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta)
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
20, 90, 120, // Color fondo notificación: cian oscuro (aurora cian)
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
70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta)
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",
"Volcán",
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
50, 50, 55, // Color fondo notificación: gris oscuro (ceniza)
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
120, 30, 15, // Color fondo notificación: naranja-rojo oscuro (erupción)
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
150, 70, 25, // Color fondo notificación: naranja oscuro (lava)
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
80, 60, 50, // Color fondo notificación: gris-naranja oscuro (enfriamiento)
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...)
));
}
// ============================================================================
// INTERFAZ UNIFICADA (PHASE 2)
// ============================================================================
void ThemeManager::switchToTheme(int theme_index) {
// Validar índice
if (theme_index < 0 || theme_index >= static_cast<int>(themes_.size())) {
return; // Índice inválido, no hacer nada
}
// Si ya estamos en ese tema, no hacer nada
if (theme_index == current_theme_index_ && !transitioning_) {
return;
}
// PHASE 3: Capturar snapshot del tema actual antes de cambiar
source_snapshot_ = captureCurrentSnapshot();
// Configurar transición LERP
source_theme_index_ = current_theme_index_;
target_theme_index_ = theme_index;
transitioning_ = true;
transition_progress_ = 0.0f;
// Cambiar tema activo (pero aún no visible por progress = 0.0)
current_theme_index_ = theme_index;
// Si es tema dinámico, reiniciar progreso
if (themes_[current_theme_index_]->needsUpdate()) {
themes_[current_theme_index_]->resetProgress();
}
}
void ThemeManager::update(float delta_time) {
// PHASE 3: Actualizar transición LERP si está activa
if (transitioning_) {
transition_progress_ += delta_time / transition_duration_;
if (transition_progress_ >= 1.0f) {
// Transición completada
transition_progress_ = 1.0f;
transitioning_ = false;
source_snapshot_.reset(); // Liberar snapshot (ya no se necesita)
}
}
// Actualizar animación del tema activo si es dinámico
if (themes_[current_theme_index_]->needsUpdate()) {
themes_[current_theme_index_]->update(delta_time);
}
}
void ThemeManager::cycleTheme() {
// Calcular siguiente tema con wraparound
int next_theme_index = (current_theme_index_ + 1) % static_cast<int>(themes_.size());
// Usar switchToTheme() para obtener transición LERP automáticamente
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() {
// Solo funciona si el tema actual es dinámico
if (themes_[current_theme_index_]->needsUpdate()) {
themes_[current_theme_index_]->togglePause();
}
}
// ============================================================================
// QUERIES DE COLORES
// ============================================================================
Color ThemeManager::getInterpolatedColor(size_t ball_index) const {
if (!transitioning_ || !source_snapshot_) {
// Sin transición: color directo del tema activo
return themes_[current_theme_index_]->getBallColor(ball_index, 0.0f);
}
// PHASE 3: Con transición: LERP entre snapshot origen y tema destino
Color source_color = source_snapshot_->ball_colors[ball_index % source_snapshot_->ball_colors.size()];
Color target_color = themes_[current_theme_index_]->getBallColor(ball_index, 0.0f);
return {
static_cast<int>(lerp(static_cast<float>(source_color.r), static_cast<float>(target_color.r), transition_progress_)),
static_cast<int>(lerp(static_cast<float>(source_color.g), static_cast<float>(target_color.g), transition_progress_)),
static_cast<int>(lerp(static_cast<float>(source_color.b), static_cast<float>(target_color.b), transition_progress_))
};
}
void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b,
float& bottom_r, float& bottom_g, float& bottom_b) const {
if (!transitioning_ || !source_snapshot_) {
// Sin transición: colores directos del tema activo
themes_[current_theme_index_]->getBackgroundColors(0.0f, top_r, top_g, top_b, bottom_r, bottom_g, bottom_b);
return;
}
// PHASE 3: Con transición: LERP entre snapshot origen y tema destino
float target_tr, target_tg, target_tb, target_br, target_bg, target_bb;
themes_[current_theme_index_]->getBackgroundColors(0.0f, target_tr, target_tg, target_tb, target_br, target_bg, target_bb);
top_r = lerp(source_snapshot_->bg_top_r, target_tr, transition_progress_);
top_g = lerp(source_snapshot_->bg_top_g, target_tg, transition_progress_);
top_b = lerp(source_snapshot_->bg_top_b, target_tb, transition_progress_);
bottom_r = lerp(source_snapshot_->bg_bottom_r, target_br, transition_progress_);
bottom_g = lerp(source_snapshot_->bg_bottom_g, target_bg, transition_progress_);
bottom_b = lerp(source_snapshot_->bg_bottom_b, target_bb, transition_progress_);
}
// ============================================================================
// QUERIES DE ESTADO
// ============================================================================
bool ThemeManager::isCurrentThemeDynamic() const {
return themes_[current_theme_index_]->needsUpdate();
}
const char* ThemeManager::getCurrentThemeNameEN() const {
return themes_[current_theme_index_]->getNameEN();
}
const char* ThemeManager::getCurrentThemeNameES() const {
return themes_[current_theme_index_]->getNameES();
}
void ThemeManager::getCurrentThemeTextColor(int& r, int& g, int& b) const {
if (!transitioning_ || !source_snapshot_) {
// Sin transición: color directo del tema activo
themes_[current_theme_index_]->getTextColor(r, g, b);
return;
}
// PHASE 3: Con transición: LERP entre snapshot origen y tema destino
int target_r, target_g, target_b;
themes_[current_theme_index_]->getTextColor(target_r, target_g, target_b);
r = static_cast<int>(lerp(static_cast<float>(source_snapshot_->text_color_r), static_cast<float>(target_r), transition_progress_));
g = static_cast<int>(lerp(static_cast<float>(source_snapshot_->text_color_g), static_cast<float>(target_g), transition_progress_));
b = static_cast<int>(lerp(static_cast<float>(source_snapshot_->text_color_b), static_cast<float>(target_b), transition_progress_));
}
void ThemeManager::getCurrentNotificationBackgroundColor(int& r, int& g, int& b) const {
if (!transitioning_ || !source_snapshot_) {
// Sin transición: color directo del tema activo
themes_[current_theme_index_]->getNotificationBackgroundColor(r, g, b);
return;
}
// PHASE 3: Con transición: LERP entre snapshot origen y tema destino
int target_r, target_g, target_b;
themes_[current_theme_index_]->getNotificationBackgroundColor(target_r, target_g, target_b);
r = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_r), static_cast<float>(target_r), transition_progress_));
g = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_g), static_cast<float>(target_g), transition_progress_));
b = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_b), static_cast<float>(target_b), transition_progress_));
}
Color ThemeManager::getInitialBallColor(int random_index) const {
// Obtener color inicial del tema activo (progress = 0.0f)
return themes_[current_theme_index_]->getBallColor(random_index, 0.0f);
}
// ============================================================================
// SISTEMA DE TRANSICIÓN LERP (PHASE 3)
// ============================================================================
std::unique_ptr<ThemeSnapshot> ThemeManager::captureCurrentSnapshot() const {
auto snapshot = std::make_unique<ThemeSnapshot>();
// Capturar colores de fondo
themes_[current_theme_index_]->getBackgroundColors(0.0f,
snapshot->bg_top_r, snapshot->bg_top_g, snapshot->bg_top_b,
snapshot->bg_bottom_r, snapshot->bg_bottom_g, snapshot->bg_bottom_b);
// Capturar color de texto
themes_[current_theme_index_]->getTextColor(
snapshot->text_color_r, snapshot->text_color_g, snapshot->text_color_b);
// Capturar color de fondo de notificaciones
themes_[current_theme_index_]->getNotificationBackgroundColor(
snapshot->notif_bg_r, snapshot->notif_bg_g, snapshot->notif_bg_b);
// Capturar nombres
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
snapshot->name_es = themes_[current_theme_index_]->getNameES();
// Capturar colores de pelotas (suficientes para escenario máximo: 50,000)
// Esto asegura LERP correcto incluso en escenarios grandes
snapshot->ball_colors.reserve(50000);
for (size_t i = 0; i < 50000; i++) {
snapshot->ball_colors.push_back(
themes_[current_theme_index_]->getBallColor(i, 0.0f)
);
}
return snapshot;
}