Problema: - cycleTheme() cambiaba current_theme_index_ directamente - Se saltaba todo el sistema de transición LERP - Resultado: Cambio instantáneo/abrupto con tecla B Solución: - cycleTheme() ahora delega a switchToTheme(next_index) - switchToTheme() maneja snapshot + transición automáticamente - Resultado: Transición suave de 0.5s con tecla B ✅ Ahora TODAS las formas de cambiar tema tienen LERP: ✅ Numpad 1-0: Transición suave ✅ Tecla B: Transición suave (FIXED) ✅ DEMO mode: Transición suave 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
416 lines
18 KiB
C++
416 lines
18 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(10); // 7 estáticos + 3 dinámicos
|
|
|
|
// ========================================
|
|
// TEMAS ESTÁTICOS (índices 0-6)
|
|
// ========================================
|
|
|
|
// 0: SUNSET (Atardecer) - Naranjas, rojos, amarillos, rosas
|
|
themes_.push_back(std::make_unique<StaticTheme>(
|
|
"SUNSET",
|
|
"ATARDECER",
|
|
255, 140, 60, // Color texto: naranja cálido
|
|
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",
|
|
"OCEANO",
|
|
80, 200, 255, // Color texto: azul océano
|
|
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",
|
|
"NEON",
|
|
255, 60, 255, // Color texto: magenta brillante
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
}
|
|
));
|
|
|
|
// ========================================
|
|
// TEMAS DINÁMICOS (índices 7-9)
|
|
// ========================================
|
|
|
|
// 7: 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
|
|
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
|
|
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
|
|
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
|
|
));
|
|
|
|
// 8: OCEAN WAVES (Olas Oceánicas) - Azul oscuro ↔ Turquesa (loop)
|
|
themes_.push_back(std::make_unique<DynamicTheme>(
|
|
"OCEAN WAVES",
|
|
"OLAS OCEANICAS",
|
|
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
|
|
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
|
|
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
|
|
));
|
|
|
|
// 9: NEON PULSE (Pulso Neón) - Negro → Neón brillante (rápido ping-pong)
|
|
themes_.push_back(std::make_unique<DynamicTheme>(
|
|
"NEON PULSE",
|
|
"PULSO NEON",
|
|
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
|
|
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
|
|
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
|
|
));
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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::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_));
|
|
}
|
|
|
|
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 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;
|
|
}
|