#include "theme_manager.hpp" #include "themes/dynamic_theme.hpp" #include "themes/static_theme.hpp" // ============================================================================ // 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( "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{ {.r = 255, .g = 140, .b = 0}, {.r = 255, .g = 69, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 20, .b = 147}, {.r = 255, .g = 99, .b = 71}, {.r = 255, .g = 165, .b = 0}, {.r = 255, .g = 192, .b = 203}, {.r = 220, .g = 20, .b = 60}})); // 1: OCEAN (Océano) - Azules, turquesas, blancos themes_.push_back(std::make_unique( "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{ {.r = 0, .g = 191, .b = 255}, {.r = 0, .g = 255, .b = 255}, {.r = 32, .g = 178, .b = 170}, {.r = 176, .g = 224, .b = 230}, {.r = 70, .g = 130, .b = 180}, {.r = 0, .g = 206, .b = 209}, {.r = 240, .g = 248, .b = 255}, {.r = 64, .g = 224, .b = 208}})); // 2: NEON - Cian, magenta, verde lima, amarillo vibrante themes_.push_back(std::make_unique( "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{ {.r = 0, .g = 255, .b = 255}, {.r = 255, .g = 0, .b = 255}, {.r = 50, .g = 205, .b = 50}, {.r = 255, .g = 255, .b = 0}, {.r = 255, .g = 20, .b = 147}, {.r = 0, .g = 255, .b = 127}, {.r = 138, .g = 43, .b = 226}, {.r = 255, .g = 69, .b = 0}})); // 3: FOREST (Bosque) - Verdes, marrones, amarillos otoño themes_.push_back(std::make_unique( "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{ {.r = 34, .g = 139, .b = 34}, {.r = 107, .g = 142, .b = 35}, {.r = 154, .g = 205, .b = 50}, {.r = 255, .g = 215, .b = 0}, {.r = 210, .g = 180, .b = 140}, {.r = 160, .g = 82, .b = 45}, {.r = 218, .g = 165, .b = 32}, {.r = 50, .g = 205, .b = 50}})); // 4: RGB - Círculo cromático con 24 puntos (cada 15°) themes_.push_back(std::make_unique( "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{ {.r = 255, .g = 0, .b = 0}, // 0° - Rojo puro {.r = 255, .g = 64, .b = 0}, // 15° - Rojo-Naranja {.r = 255, .g = 128, .b = 0}, // 30° - Naranja {.r = 255, .g = 191, .b = 0}, // 45° - Naranja-Amarillo {.r = 255, .g = 255, .b = 0}, // 60° - Amarillo puro {.r = 191, .g = 255, .b = 0}, // 75° - Amarillo-Verde claro {.r = 128, .g = 255, .b = 0}, // 90° - Verde-Amarillo {.r = 64, .g = 255, .b = 0}, // 105° - Verde claro-Amarillo {.r = 0, .g = 255, .b = 0}, // 120° - Verde puro {.r = 0, .g = 255, .b = 64}, // 135° - Verde-Cian claro {.r = 0, .g = 255, .b = 128}, // 150° - Verde-Cian {.r = 0, .g = 255, .b = 191}, // 165° - Verde claro-Cian {.r = 0, .g = 255, .b = 255}, // 180° - Cian puro {.r = 0, .g = 191, .b = 255}, // 195° - Cian-Azul claro {.r = 0, .g = 128, .b = 255}, // 210° - Azul-Cian {.r = 0, .g = 64, .b = 255}, // 225° - Azul claro-Cian {.r = 0, .g = 0, .b = 255}, // 240° - Azul puro {.r = 64, .g = 0, .b = 255}, // 255° - Azul-Magenta claro {.r = 128, .g = 0, .b = 255}, // 270° - Azul-Magenta {.r = 191, .g = 0, .b = 255}, // 285° - Azul claro-Magenta {.r = 255, .g = 0, .b = 255}, // 300° - Magenta puro {.r = 255, .g = 0, .b = 191}, // 315° - Magenta-Rojo claro {.r = 255, .g = 0, .b = 128}, // 330° - Magenta-Rojo {.r = 255, .g = 0, .b = 64} // 345° - Magenta claro-Rojo })); // 5: MONOCHROME (Monocromo) - Fondo negro degradado, sprites blancos themes_.push_back(std::make_unique( "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{ {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}, {.r = 255, .g = 255, .b = 255}})); // 6: LAVENDER (Lavanda) - Degradado violeta oscuro → azul medianoche, pelotas amarillo dorado themes_.push_back(std::make_unique( "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{ {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}, {.r = 255, .g = 215, .b = 0}})); // 7: CRIMSON (Carmesí) - Fondo negro-rojo oscuro, pelotas rojas uniformes themes_.push_back(std::make_unique( "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{ {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}, {.r = 220, .g = 20, .b = 60}})); // 8: EMERALD (Esmeralda) - Fondo negro-verde oscuro, pelotas verdes uniformes themes_.push_back(std::make_unique( "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{ {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}, {.r = 50, .g = 205, .b = 50}})); // ======================================== // TEMAS DINÁMICOS (índices 9-14) // ======================================== // 9: SUNRISE (Amanecer) - Noche → Alba → Día (loop) themes_.push_back(std::make_unique( "Sunrise", "Amanecer", 255, 200, 100, // Color texto: amarillo cálido std::vector{ // Keyframe 0: Noche oscura (estado inicial) { .bg_top_r = 20.0f / 255.0f, .bg_top_g = 25.0f / 255.0f, .bg_top_b = 60.0f / 255.0f, // Fondo superior: azul medianoche .bg_bottom_r = 10.0f / 255.0f, .bg_bottom_g = 10.0f / 255.0f, .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: azul muy oscuro .notif_bg_r = 20, .notif_bg_g = 30, .notif_bg_b = 80, // Color fondo notificación: azul oscuro (noche) .ball_colors = std::vector{ {.r = 100, .g = 100, .b = 150}, {.r = 120, .g = 120, .b = 170}, {.r = 90, .g = 90, .b = 140}, {.r = 110, .g = 110, .b = 160}, {.r = 95, .g = 95, .b = 145}, {.r = 105, .g = 105, .b = 155}, {.r = 100, .g = 100, .b = 150}, {.r = 115, .g = 115, .b = 165}}, .duration = 0.0f // Sin transición (estado inicial) }, // Keyframe 1: Alba naranja-rosa { .bg_top_r = 180.0f / 255.0f, .bg_top_g = 100.0f / 255.0f, .bg_top_b = 120.0f / 255.0f, // Fondo superior: naranja-rosa .bg_bottom_r = 255.0f / 255.0f, .bg_bottom_g = 140.0f / 255.0f, .bg_bottom_b = 100.0f / 255.0f, // Fondo inferior: naranja cálido .notif_bg_r = 140, .notif_bg_g = 60, .notif_bg_b = 80, // Color fondo notificación: naranja-rojo oscuro (alba) .ball_colors = std::vector{{.r = 255, .g = 180, .b = 100}, {.r = 255, .g = 160, .b = 80}, {.r = 255, .g = 200, .b = 120}, {.r = 255, .g = 150, .b = 90}, {.r = 255, .g = 190, .b = 110}, {.r = 255, .g = 170, .b = 95}, {.r = 255, .g = 185, .b = 105}, {.r = 255, .g = 165, .b = 88}}, .duration = 4.0f // 4 segundos para llegar aquí }, // Keyframe 2: Día brillante amarillo { .bg_top_r = 255.0f / 255.0f, .bg_top_g = 240.0f / 255.0f, .bg_top_b = 180.0f / 255.0f, // Fondo superior: amarillo claro .bg_bottom_r = 255.0f / 255.0f, .bg_bottom_g = 255.0f / 255.0f, .bg_bottom_b = 220.0f / 255.0f, // Fondo inferior: amarillo muy claro .notif_bg_r = 200, .notif_bg_g = 180, .notif_bg_b = 140, // Color fondo notificación: amarillo oscuro (día) .ball_colors = std::vector{{.r = 255, .g = 255, .b = 200}, {.r = 255, .g = 255, .b = 180}, {.r = 255, .g = 255, .b = 220}, {.r = 255, .g = 255, .b = 190}, {.r = 255, .g = 255, .b = 210}, {.r = 255, .g = 255, .b = 185}, {.r = 255, .g = 255, .b = 205}, {.r = 255, .g = 255, .b = 195}}, .duration = 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( "Ocean Waves", "Olas Oceánicas", 100, 220, 255, // Color texto: cian claro std::vector{ // Keyframe 0: Profundidad oceánica (azul oscuro) { .bg_top_r = 20.0f / 255.0f, .bg_top_g = 50.0f / 255.0f, .bg_top_b = 100.0f / 255.0f, // Fondo superior: azul marino .bg_bottom_r = 10.0f / 255.0f, .bg_bottom_g = 30.0f / 255.0f, .bg_bottom_b = 60.0f / 255.0f, // Fondo inferior: azul muy oscuro .notif_bg_r = 10, .notif_bg_g = 30, .notif_bg_b = 70, // Color fondo notificación: azul muy oscuro (profundidad) .ball_colors = std::vector{ {.r = 60, .g = 100, .b = 180}, {.r = 50, .g = 90, .b = 170}, {.r = 70, .g = 110, .b = 190}, {.r = 55, .g = 95, .b = 175}, {.r = 65, .g = 105, .b = 185}, {.r = 58, .g = 98, .b = 172}, {.r = 62, .g = 102, .b = 182}, {.r = 52, .g = 92, .b = 168}}, .duration = 0.0f // Estado inicial }, // Keyframe 1: Aguas poco profundas (turquesa brillante) { .bg_top_r = 100.0f / 255.0f, .bg_top_g = 200.0f / 255.0f, .bg_top_b = 230.0f / 255.0f, // Fondo superior: turquesa claro .bg_bottom_r = 50.0f / 255.0f, .bg_bottom_g = 150.0f / 255.0f, .bg_bottom_b = 200.0f / 255.0f, // Fondo inferior: turquesa medio .notif_bg_r = 30, .notif_bg_g = 100, .notif_bg_b = 140, // Color fondo notificación: turquesa oscuro (aguas poco profundas) .ball_colors = std::vector{{.r = 100, .g = 220, .b = 255}, {.r = 90, .g = 210, .b = 245}, {.r = 110, .g = 230, .b = 255}, {.r = 95, .g = 215, .b = 250}, {.r = 105, .g = 225, .b = 255}, {.r = 98, .g = 218, .b = 248}, {.r = 102, .g = 222, .b = 252}, {.r = 92, .g = 212, .b = 242}}, .duration = 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( "Neon Pulse", "Pulso Neón", 255, 60, 255, // Color texto: magenta brillante std::vector{ // Keyframe 0: Apagado (negro) { .bg_top_r = 0.0f / 255.0f, .bg_top_g = 0.0f / 255.0f, .bg_top_b = 0.0f / 255.0f, // Fondo superior: negro .bg_bottom_r = 0.0f / 255.0f, .bg_bottom_g = 0.0f / 255.0f, .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: negro .notif_bg_r = 30, .notif_bg_g = 30, .notif_bg_b = 30, // Color fondo notificación: gris muy oscuro (apagado) .ball_colors = std::vector{ {.r = 40, .g = 40, .b = 40}, {.r = 50, .g = 50, .b = 50}, {.r = 45, .g = 45, .b = 45}, {.r = 48, .g = 48, .b = 48}, {.r = 42, .g = 42, .b = 42}, {.r = 47, .g = 47, .b = 47}, {.r = 44, .g = 44, .b = 44}, {.r = 46, .g = 46, .b = 46}}, .duration = 0.0f // Estado inicial }, // Keyframe 1: Encendido (neón cian-magenta) { .bg_top_r = 20.0f / 255.0f, .bg_top_g = 20.0f / 255.0f, .bg_top_b = 40.0f / 255.0f, // Fondo superior: azul oscuro .bg_bottom_r = 0.0f / 255.0f, .bg_bottom_g = 0.0f / 255.0f, .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: negro .notif_bg_r = 60, .notif_bg_g = 0, .notif_bg_b = 80, // Color fondo notificación: púrpura oscuro (neón encendido) .ball_colors = std::vector{{.r = 0, .g = 255, .b = 255}, {.r = 255, .g = 0, .b = 255}, {.r = 0, .g = 255, .b = 200}, {.r = 255, .g = 50, .b = 255}, {.r = 50, .g = 255, .b = 255}, {.r = 255, .g = 0, .b = 200}, {.r = 0, .g = 255, .b = 230}, {.r = 255, .g = 80, .b = 255}}, .duration = 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( "Fire", "Fuego", 255, 150, 80, // Color texto: naranja cálido std::vector{ // Keyframe 0: Brasas oscuras (estado inicial) { .bg_top_r = 60.0f / 255.0f, .bg_top_g = 20.0f / 255.0f, .bg_top_b = 10.0f / 255.0f, // Fondo superior: rojo muy oscuro .bg_bottom_r = 20.0f / 255.0f, .bg_bottom_g = 10.0f / 255.0f, .bg_bottom_b = 0.0f / 255.0f, // Fondo inferior: casi negro .notif_bg_r = 70, .notif_bg_g = 20, .notif_bg_b = 10, // Color fondo notificación: rojo muy oscuro (brasas) .ball_colors = std::vector{ {.r = 120, .g = 40, .b = 20}, {.r = 140, .g = 35, .b = 15}, {.r = 130, .g = 38, .b = 18}, {.r = 125, .g = 42, .b = 22}, {.r = 135, .g = 37, .b = 16}, {.r = 128, .g = 40, .b = 20}, {.r = 132, .g = 39, .b = 19}, {.r = 138, .g = 36, .b = 17}}, .duration = 0.0f // Estado inicial }, // Keyframe 1: Llamas naranjas (transición) { .bg_top_r = 180.0f / 255.0f, .bg_top_g = 80.0f / 255.0f, .bg_top_b = 20.0f / 255.0f, // Fondo superior: naranja fuerte .bg_bottom_r = 100.0f / 255.0f, .bg_bottom_g = 30.0f / 255.0f, .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro .notif_bg_r = 110, .notif_bg_g = 40, .notif_bg_b = 10, // Color fondo notificación: naranja-rojo oscuro (llamas) .ball_colors = std::vector{{.r = 255, .g = 140, .b = 0}, {.r = 255, .g = 120, .b = 10}, {.r = 255, .g = 160, .b = 20}, {.r = 255, .g = 130, .b = 5}, {.r = 255, .g = 150, .b = 15}, {.r = 255, .g = 125, .b = 8}, {.r = 255, .g = 145, .b = 12}, {.r = 255, .g = 135, .b = 18}}, .duration = 3.5f // 3.5 segundos para llegar }, // Keyframe 2: Inferno brillante (clímax) { .bg_top_r = 255.0f / 255.0f, .bg_top_g = 180.0f / 255.0f, .bg_top_b = 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante .bg_bottom_r = 220.0f / 255.0f, .bg_bottom_g = 100.0f / 255.0f, .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: naranja intenso .notif_bg_r = 160, .notif_bg_g = 80, .notif_bg_b = 30, // Color fondo notificación: naranja oscuro (inferno) .ball_colors = std::vector{{.r = 255, .g = 220, .b = 100}, {.r = 255, .g = 200, .b = 80}, {.r = 255, .g = 240, .b = 120}, {.r = 255, .g = 210, .b = 90}, {.r = 255, .g = 230, .b = 110}, {.r = 255, .g = 205, .b = 85}, {.r = 255, .g = 225, .b = 105}, {.r = 255, .g = 215, .b = 95}}, .duration = 3.0f // 3 segundos para llegar }, // Keyframe 3: Vuelta a llamas (antes de reiniciar loop) { .bg_top_r = 180.0f / 255.0f, .bg_top_g = 80.0f / 255.0f, .bg_top_b = 20.0f / 255.0f, // Fondo superior: naranja fuerte .bg_bottom_r = 100.0f / 255.0f, .bg_bottom_g = 30.0f / 255.0f, .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro .notif_bg_r = 110, .notif_bg_g = 40, .notif_bg_b = 10, // Color fondo notificación: naranja-rojo oscuro (llamas) .ball_colors = std::vector{{.r = 255, .g = 140, .b = 0}, {.r = 255, .g = 120, .b = 10}, {.r = 255, .g = 160, .b = 20}, {.r = 255, .g = 130, .b = 5}, {.r = 255, .g = 150, .b = 15}, {.r = 255, .g = 125, .b = 8}, {.r = 255, .g = 145, .b = 12}, {.r = 255, .g = 135, .b = 18}}, .duration = 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( "Aurora", "Aurora", 150, 255, 200, // Color texto: verde claro std::vector{ // Keyframe 0: Verde aurora (estado inicial) { .bg_top_r = 30.0f / 255.0f, .bg_top_g = 80.0f / 255.0f, .bg_top_b = 60.0f / 255.0f, // Fondo superior: verde oscuro .bg_bottom_r = 10.0f / 255.0f, .bg_bottom_g = 20.0f / 255.0f, .bg_bottom_b = 30.0f / 255.0f, // Fondo inferior: azul muy oscuro .notif_bg_r = 15, .notif_bg_g = 50, .notif_bg_b = 40, // Color fondo notificación: verde muy oscuro (aurora verde) .ball_colors = std::vector{ {.r = 100, .g = 255, .b = 180}, {.r = 80, .g = 240, .b = 160}, {.r = 120, .g = 255, .b = 200}, {.r = 90, .g = 245, .b = 170}, {.r = 110, .g = 255, .b = 190}, {.r = 85, .g = 242, .b = 165}, {.r = 105, .g = 252, .b = 185}, {.r = 95, .g = 248, .b = 175}}, .duration = 0.0f // Estado inicial }, // Keyframe 1: Violeta aurora (transición) { .bg_top_r = 120.0f / 255.0f, .bg_top_g = 60.0f / 255.0f, .bg_top_b = 180.0f / 255.0f, // Fondo superior: violeta .bg_bottom_r = 40.0f / 255.0f, .bg_bottom_g = 20.0f / 255.0f, .bg_bottom_b = 80.0f / 255.0f, // Fondo inferior: violeta oscuro .notif_bg_r = 70, .notif_bg_g = 30, .notif_bg_b = 100, // Color fondo notificación: violeta oscuro (aurora violeta) .ball_colors = std::vector{{.r = 200, .g = 100, .b = 255}, {.r = 180, .g = 80, .b = 240}, {.r = 220, .g = 120, .b = 255}, {.r = 190, .g = 90, .b = 245}, {.r = 210, .g = 110, .b = 255}, {.r = 185, .g = 85, .b = 242}, {.r = 205, .g = 105, .b = 252}, {.r = 195, .g = 95, .b = 248}}, .duration = 5.0f // 5 segundos para llegar (transición lenta) }, // Keyframe 2: Cian aurora (clímax) { .bg_top_r = 60.0f / 255.0f, .bg_top_g = 180.0f / 255.0f, .bg_top_b = 220.0f / 255.0f, // Fondo superior: cian brillante .bg_bottom_r = 20.0f / 255.0f, .bg_bottom_g = 80.0f / 255.0f, .bg_bottom_b = 120.0f / 255.0f, // Fondo inferior: azul oscuro .notif_bg_r = 20, .notif_bg_g = 90, .notif_bg_b = 120, // Color fondo notificación: cian oscuro (aurora cian) .ball_colors = std::vector{{.r = 100, .g = 220, .b = 255}, {.r = 80, .g = 200, .b = 240}, {.r = 120, .g = 240, .b = 255}, {.r = 90, .g = 210, .b = 245}, {.r = 110, .g = 230, .b = 255}, {.r = 85, .g = 205, .b = 242}, {.r = 105, .g = 225, .b = 252}, {.r = 95, .g = 215, .b = 248}}, .duration = 4.5f // 4.5 segundos para llegar }, // Keyframe 3: Vuelta a violeta (antes de reiniciar) { .bg_top_r = 120.0f / 255.0f, .bg_top_g = 60.0f / 255.0f, .bg_top_b = 180.0f / 255.0f, // Fondo superior: violeta .bg_bottom_r = 40.0f / 255.0f, .bg_bottom_g = 20.0f / 255.0f, .bg_bottom_b = 80.0f / 255.0f, // Fondo inferior: violeta oscuro .notif_bg_r = 70, .notif_bg_g = 30, .notif_bg_b = 100, // Color fondo notificación: violeta oscuro (aurora violeta) .ball_colors = std::vector{{.r = 200, .g = 100, .b = 255}, {.r = 180, .g = 80, .b = 240}, {.r = 220, .g = 120, .b = 255}, {.r = 190, .g = 90, .b = 245}, {.r = 210, .g = 110, .b = 255}, {.r = 185, .g = 85, .b = 242}, {.r = 205, .g = 105, .b = 252}, {.r = 195, .g = 95, .b = 248}}, .duration = 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( "Volcanic", "Volcán", 200, 120, 80, // Color texto: naranja apagado std::vector{ // Keyframe 0: Ceniza oscura (pre-erupción) { .bg_top_r = 40.0f / 255.0f, .bg_top_g = 40.0f / 255.0f, .bg_top_b = 45.0f / 255.0f, // Fondo superior: gris oscuro .bg_bottom_r = 20.0f / 255.0f, .bg_bottom_g = 15.0f / 255.0f, .bg_bottom_b = 15.0f / 255.0f, // Fondo inferior: casi negro .notif_bg_r = 50, .notif_bg_g = 50, .notif_bg_b = 55, // Color fondo notificación: gris oscuro (ceniza) .ball_colors = std::vector{ {.r = 80, .g = 80, .b = 90}, {.r = 75, .g = 75, .b = 85}, {.r = 85, .g = 85, .b = 95}, {.r = 78, .g = 78, .b = 88}, {.r = 82, .g = 82, .b = 92}, {.r = 76, .g = 76, .b = 86}, {.r = 84, .g = 84, .b = 94}, {.r = 79, .g = 79, .b = 89}}, .duration = 0.0f // Estado inicial }, // Keyframe 1: Erupción naranja-roja (explosión) { .bg_top_r = 180.0f / 255.0f, .bg_top_g = 60.0f / 255.0f, .bg_top_b = 30.0f / 255.0f, // Fondo superior: naranja-rojo .bg_bottom_r = 80.0f / 255.0f, .bg_bottom_g = 20.0f / 255.0f, .bg_bottom_b = 10.0f / 255.0f, // Fondo inferior: rojo oscuro .notif_bg_r = 120, .notif_bg_g = 30, .notif_bg_b = 15, // Color fondo notificación: naranja-rojo oscuro (erupción) .ball_colors = std::vector{{.r = 255, .g = 80, .b = 40}, {.r = 255, .g = 100, .b = 50}, {.r = 255, .g = 70, .b = 35}, {.r = 255, .g = 90, .b = 45}, {.r = 255, .g = 75, .b = 38}, {.r = 255, .g = 95, .b = 48}, {.r = 255, .g = 85, .b = 42}, {.r = 255, .g = 78, .b = 40}}, .duration = 3.0f // 3 segundos para erupción (rápido) }, // Keyframe 2: Lava brillante (clímax) { .bg_top_r = 220.0f / 255.0f, .bg_top_g = 120.0f / 255.0f, .bg_top_b = 40.0f / 255.0f, // Fondo superior: naranja brillante .bg_bottom_r = 180.0f / 255.0f, .bg_bottom_g = 60.0f / 255.0f, .bg_bottom_b = 20.0f / 255.0f, // Fondo inferior: naranja-rojo .notif_bg_r = 150, .notif_bg_g = 70, .notif_bg_b = 25, // Color fondo notificación: naranja oscuro (lava) .ball_colors = std::vector{{.r = 255, .g = 180, .b = 80}, {.r = 255, .g = 200, .b = 100}, {.r = 255, .g = 170, .b = 70}, {.r = 255, .g = 190, .b = 90}, {.r = 255, .g = 175, .b = 75}, {.r = 255, .g = 195, .b = 95}, {.r = 255, .g = 185, .b = 85}, {.r = 255, .g = 178, .b = 78}}, .duration = 3.5f // 3.5 segundos para lava máxima }, // Keyframe 3: Enfriamiento (vuelta a ceniza gradual) { .bg_top_r = 100.0f / 255.0f, .bg_top_g = 80.0f / 255.0f, .bg_top_b = 70.0f / 255.0f, // Fondo superior: gris-naranja .bg_bottom_r = 50.0f / 255.0f, .bg_bottom_g = 40.0f / 255.0f, .bg_bottom_b = 35.0f / 255.0f, // Fondo inferior: gris oscuro .notif_bg_r = 80, .notif_bg_g = 60, .notif_bg_b = 50, // Color fondo notificación: gris-naranja oscuro (enfriamiento) .ball_colors = std::vector{{.r = 150, .g = 120, .b = 100}, {.r = 140, .g = 110, .b = 90}, {.r = 160, .g = 130, .b = 110}, {.r = 145, .g = 115, .b = 95}, {.r = 155, .g = 125, .b = 105}, {.r = 142, .g = 112, .b = 92}, {.r = 158, .g = 128, .b = 108}, {.r = 148, .g = 118, .b = 98}}, .duration = 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(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(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(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 // ============================================================================ auto ThemeManager::getInterpolatedColor(size_t ball_index) const -> Color { 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 { .r = static_cast(lerp(static_cast(source_color.r), static_cast(target_color.r), transition_progress_)), .g = static_cast(lerp(static_cast(source_color.g), static_cast(target_color.g), transition_progress_)), .b = static_cast(lerp(static_cast(source_color.b), static_cast(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; float target_tg; float target_tb; float target_br; float target_bg; float 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 // ============================================================================ auto ThemeManager::isCurrentThemeDynamic() const -> bool { return themes_[current_theme_index_]->needsUpdate(); } auto ThemeManager::getCurrentThemeNameEN() const -> const char* { return themes_[current_theme_index_]->getNameEN(); } auto ThemeManager::getCurrentThemeNameES() const -> const char* { 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; int target_g; int target_b; themes_[current_theme_index_]->getTextColor(target_r, target_g, target_b); r = static_cast(lerp(static_cast(source_snapshot_->text_color_r), static_cast(target_r), transition_progress_)); g = static_cast(lerp(static_cast(source_snapshot_->text_color_g), static_cast(target_g), transition_progress_)); b = static_cast(lerp(static_cast(source_snapshot_->text_color_b), static_cast(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; int target_g; int target_b; themes_[current_theme_index_]->getNotificationBackgroundColor(target_r, target_g, target_b); r = static_cast(lerp(static_cast(source_snapshot_->notif_bg_r), static_cast(target_r), transition_progress_)); g = static_cast(lerp(static_cast(source_snapshot_->notif_bg_g), static_cast(target_g), transition_progress_)); b = static_cast(lerp(static_cast(source_snapshot_->notif_bg_b), static_cast(target_b), transition_progress_)); } auto ThemeManager::getInitialBallColor(int random_index) const -> Color { // 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) // ============================================================================ auto ThemeManager::captureCurrentSnapshot() const -> std::unique_ptr { auto snapshot = std::make_unique(); // 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 para el máximo real de esta sesión // (SCENE_BALLS_8 o más si hay escenario custom) snapshot->ball_colors.reserve(max_ball_count_); for (int i = 0; i < max_ball_count_; i++) { snapshot->ball_colors.push_back( themes_[current_theme_index_]->getBallColor(i, 0.0f)); } return snapshot; }