PHASE 2: Refactorización completa del sistema de temas unificado
Arquitectura polimórfica implementada: - Jerarquía: Theme (base) → StaticTheme / DynamicTheme (derivadas) - Vector unificado de 10 temas (7 estáticos + 3 dinámicos) - Eliminada lógica dual (if(dynamic_theme_active_) scattered) Nuevos archivos: - source/themes/theme.h: Interfaz base abstracta - source/themes/static_theme.h/cpp: Temas estáticos (1 keyframe) - source/themes/dynamic_theme.h/cpp: Temas dinámicos (N keyframes animados) - source/theme_manager.h/cpp: Gestión unificada de temas Mejoras de API: - switchToTheme(0-9): Cambio a cualquier tema (índice 0-9) - cycleTheme(): Cicla por todos los temas (Tecla B) - update(delta_time): Actualización simplificada - getInterpolatedColor(idx): Sin parámetro balls_ Bugs corregidos: - Tecla B ahora cicla TODOS los 10 temas (antes solo 6) - DEMO mode elige de TODOS los temas (antes excluía LAVENDER + dinámicos) - Eliminada duplicación de keyframes en temas dinámicos (loop=true lo maneja) Código reducido: - theme_manager.cpp: 558 → 320 líneas (-43%) - engine.cpp: Eliminados ~470 líneas de lógica de temas - Complejidad significativamente reducida Preparado para PHASE 3 (LERP universal entre cualquier par de temas) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ if (NOT SDL3_FOUND)
|
||||
endif()
|
||||
|
||||
# Archivos fuente (excluir main_old.cpp)
|
||||
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp)
|
||||
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp source/themes/*.cpp)
|
||||
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
|
||||
|
||||
# Comprobar si se encontraron archivos fuente
|
||||
|
||||
@@ -48,6 +48,16 @@ struct Color {
|
||||
int r, g, b; // Componentes rojo, verde, azul (0-255)
|
||||
};
|
||||
|
||||
// Estructura de tema de colores estático
|
||||
struct ThemeColors {
|
||||
const char* name_en; // Nombre en inglés (para debug)
|
||||
const char* name_es; // Nombre en español (para display)
|
||||
int text_color_r, text_color_g, text_color_b; // Color del texto del tema
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
std::vector<Color> ball_colors;
|
||||
};
|
||||
|
||||
// Estructura para keyframe de tema dinámico
|
||||
struct DynamicThemeKeyframe {
|
||||
// Fondo degradado
|
||||
@@ -62,14 +72,8 @@ struct DynamicThemeKeyframe {
|
||||
float duration;
|
||||
};
|
||||
|
||||
// Estructura para tema dinámico (animado)
|
||||
struct DynamicTheme {
|
||||
const char* name_en; // Nombre en inglés
|
||||
const char* name_es; // Nombre en español
|
||||
int text_color_r, text_color_g, text_color_b; // Color del texto del tema
|
||||
std::vector<DynamicThemeKeyframe> keyframes; // Mínimo 2 keyframes
|
||||
bool loop; // ¿Volver al inicio al terminar?
|
||||
};
|
||||
// NOTA: La clase DynamicTheme (tema dinámico animado) está definida en themes/dynamic_theme.h
|
||||
// Esta estructura de datos es solo para definir keyframes que se pasan al constructor
|
||||
|
||||
// Enum para dirección de gravedad
|
||||
enum class GravityDirection {
|
||||
|
||||
@@ -204,8 +204,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
|
||||
srand(static_cast<unsigned>(time(nullptr)));
|
||||
dbg_init(renderer_);
|
||||
initializeThemes();
|
||||
initializeDynamicThemes();
|
||||
|
||||
// Inicializar ThemeManager
|
||||
theme_manager_ = std::make_unique<ThemeManager>();
|
||||
theme_manager_->initialize();
|
||||
|
||||
initBalls(scenario_);
|
||||
}
|
||||
|
||||
@@ -289,27 +292,8 @@ void Engine::update() {
|
||||
// Actualizar Modo DEMO (auto-play)
|
||||
updateDemoMode();
|
||||
|
||||
// Actualizar transición de tema (LERP)
|
||||
if (transitioning_) {
|
||||
transition_progress_ += delta_time_ / transition_duration_;
|
||||
|
||||
if (transition_progress_ >= 1.0f) {
|
||||
// Transición completa
|
||||
transition_progress_ = 1.0f;
|
||||
current_theme_ = target_theme_;
|
||||
transitioning_ = false;
|
||||
|
||||
// Actualizar colores de las pelotas al tema final
|
||||
const ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
||||
for (size_t i = 0; i < balls_.size(); i++) {
|
||||
size_t color_index = i % theme.ball_colors.size();
|
||||
balls_[i]->setColor(theme.ball_colors[color_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar tema dinámico (animación de keyframes)
|
||||
updateDynamicTheme();
|
||||
// Actualizar transiciones de temas (delegado a ThemeManager)
|
||||
theme_manager_->update(delta_time_);
|
||||
}
|
||||
|
||||
void Engine::handleEvents() {
|
||||
@@ -437,53 +421,118 @@ void Engine::handleEvents() {
|
||||
|
||||
// Ciclar temas de color (movido de T a B)
|
||||
case SDLK_B:
|
||||
// Ciclar al siguiente tema con transición suave (LERP)
|
||||
{
|
||||
ColorTheme next_theme = static_cast<ColorTheme>((static_cast<int>(current_theme_) + 1) % (sizeof(themes_) / sizeof(themes_[0])));
|
||||
startThemeTransition(next_theme);
|
||||
// Ciclar al siguiente tema con transición suave
|
||||
theme_manager_->cycleTheme();
|
||||
|
||||
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
||||
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;
|
||||
|
||||
// Temas de colores con teclado numérico (con transición suave)
|
||||
case SDLK_KP_1:
|
||||
startThemeTransition(ColorTheme::SUNSET);
|
||||
theme_manager_->switchToTheme(0); // SUNSET
|
||||
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;
|
||||
|
||||
case SDLK_KP_2:
|
||||
startThemeTransition(ColorTheme::OCEAN);
|
||||
theme_manager_->switchToTheme(1); // OCEAN
|
||||
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;
|
||||
|
||||
case SDLK_KP_3:
|
||||
startThemeTransition(ColorTheme::NEON);
|
||||
theme_manager_->switchToTheme(2); // NEON
|
||||
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;
|
||||
|
||||
case SDLK_KP_4:
|
||||
startThemeTransition(ColorTheme::FOREST);
|
||||
theme_manager_->switchToTheme(3); // FOREST
|
||||
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;
|
||||
|
||||
case SDLK_KP_5:
|
||||
startThemeTransition(ColorTheme::RGB);
|
||||
theme_manager_->switchToTheme(4); // RGB
|
||||
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;
|
||||
|
||||
case SDLK_KP_6:
|
||||
startThemeTransition(ColorTheme::MONOCHROME);
|
||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
||||
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;
|
||||
|
||||
case SDLK_KP_7:
|
||||
startThemeTransition(ColorTheme::LAVENDER);
|
||||
theme_manager_->switchToTheme(6); // LAVENDER
|
||||
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;
|
||||
|
||||
// Temas dinámicos (animados) - Solo Numpad 8/9/0 (teclas normales usadas para escenarios)
|
||||
case SDLK_KP_8:
|
||||
activateDynamicTheme(0); // SUNRISE
|
||||
theme_manager_->switchToTheme(7); // 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;
|
||||
|
||||
case SDLK_KP_9:
|
||||
activateDynamicTheme(1); // OCEAN WAVES
|
||||
theme_manager_->switchToTheme(8); // OCEAN WAVES
|
||||
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;
|
||||
|
||||
case SDLK_KP_0:
|
||||
activateDynamicTheme(2); // NEON PULSE
|
||||
theme_manager_->switchToTheme(9); // NEON PULSE
|
||||
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;
|
||||
|
||||
// Cambio de sprite/textura dinámico
|
||||
@@ -610,7 +659,7 @@ void Engine::handleEvents() {
|
||||
case SDLK_D:
|
||||
// Shift+D = Pausar tema dinámico
|
||||
if (event.key.mod & SDL_KMOD_SHIFT) {
|
||||
pauseDynamicTheme();
|
||||
theme_manager_->pauseDynamic();
|
||||
} else {
|
||||
// D sin Shift = Toggle modo DEMO
|
||||
if (current_app_mode_ == AppMode::DEMO) {
|
||||
@@ -675,8 +724,40 @@ void Engine::render() {
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); // Negro para barras de letterbox/integer
|
||||
SDL_RenderClear(renderer_);
|
||||
|
||||
// Renderizar fondo degradado en lugar de color sólido
|
||||
renderGradientBackground();
|
||||
// Renderizar fondo degradado (delegado a ThemeManager)
|
||||
{
|
||||
float top_r, top_g, top_b, bottom_r, bottom_g, bottom_b;
|
||||
theme_manager_->getBackgroundColors(top_r, top_g, top_b, bottom_r, bottom_g, bottom_b);
|
||||
|
||||
// Crear quad de pantalla completa con degradado
|
||||
SDL_Vertex bg_vertices[4];
|
||||
|
||||
// Vértice superior izquierdo
|
||||
bg_vertices[0].position = {0, 0};
|
||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
||||
bg_vertices[0].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice superior derecho
|
||||
bg_vertices[1].position = {static_cast<float>(current_screen_width_), 0};
|
||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
||||
bg_vertices[1].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice inferior derecho
|
||||
bg_vertices[2].position = {static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
||||
bg_vertices[2].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Vértice inferior izquierdo
|
||||
bg_vertices[3].position = {0, static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
||||
bg_vertices[3].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Índices para 2 triángulos
|
||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
// Renderizar sin textura (nullptr)
|
||||
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
|
||||
}
|
||||
|
||||
// Limpiar batches del frame anterior
|
||||
batch_vertices_.clear();
|
||||
@@ -701,7 +782,7 @@ void Engine::render() {
|
||||
// Renderizar en orden de profundidad (fondo → frente)
|
||||
for (size_t idx : render_order) {
|
||||
SDL_FRect pos = balls_[idx]->getPosition();
|
||||
Color color = getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
Color color = theme_manager_->getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
float brightness = balls_[idx]->getDepthBrightness();
|
||||
float depth_scale = balls_[idx]->getDepthScale();
|
||||
|
||||
@@ -720,7 +801,7 @@ void Engine::render() {
|
||||
size_t idx = 0;
|
||||
for (auto& ball : balls_) {
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
Color color = getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
Color color = theme_manager_->getInterpolatedColor(idx); // Usar color interpolado (LERP)
|
||||
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b, 1.0f);
|
||||
idx++;
|
||||
}
|
||||
@@ -732,26 +813,10 @@ void Engine::render() {
|
||||
}
|
||||
|
||||
if (show_text_) {
|
||||
// Obtener datos del tema actual (estático o dinámico)
|
||||
// Obtener datos del tema actual (delegado a ThemeManager)
|
||||
int text_color_r, text_color_g, text_color_b;
|
||||
const char* theme_name_es = nullptr;
|
||||
|
||||
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
||||
// Tema dinámico activo
|
||||
const DynamicTheme& dyn_theme = dynamic_themes_[current_dynamic_theme_index_];
|
||||
text_color_r = dyn_theme.text_color_r;
|
||||
text_color_g = dyn_theme.text_color_g;
|
||||
text_color_b = dyn_theme.text_color_b;
|
||||
theme_name_es = dyn_theme.name_es;
|
||||
} else {
|
||||
// Tema estático
|
||||
int theme_idx = static_cast<int>(current_theme_);
|
||||
const ThemeColors& current = themes_[theme_idx];
|
||||
text_color_r = current.text_color_r;
|
||||
text_color_g = current.text_color_g;
|
||||
text_color_b = current.text_color_b;
|
||||
theme_name_es = current.name_es;
|
||||
}
|
||||
theme_manager_->getCurrentThemeTextColor(text_color_r, text_color_g, text_color_b);
|
||||
const char* theme_name_es = theme_manager_->getCurrentThemeNameES();
|
||||
|
||||
// Texto del número de pelotas con color del tema
|
||||
dbg_print(text_pos_, 8, text_.c_str(), text_color_r, text_color_g, text_color_b);
|
||||
@@ -803,13 +868,8 @@ void Engine::render() {
|
||||
dbg_print(8, 56, gravity_dir_text.c_str(), 255, 255, 0); // Amarillo para dirección
|
||||
}
|
||||
|
||||
// Debug: Mostrar tema actual
|
||||
std::string theme_text;
|
||||
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
||||
theme_text = std::string("THEME ") + dynamic_themes_[current_dynamic_theme_index_].name_en;
|
||||
} else {
|
||||
theme_text = std::string("THEME ") + themes_[static_cast<int>(current_theme_)].name_en;
|
||||
}
|
||||
// Debug: Mostrar tema actual (delegado a ThemeManager)
|
||||
std::string theme_text = std::string("THEME ") + theme_manager_->getCurrentThemeNameEN();
|
||||
dbg_print(8, 64, theme_text.c_str(), 255, 255, 128); // Amarillo claro para tema
|
||||
|
||||
// Debug: Mostrar modo de simulación actual
|
||||
@@ -872,20 +932,10 @@ void Engine::initBalls(int value) {
|
||||
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGN; // Velocidad en X
|
||||
const float VY = ((rand() % 60) - 30) * 0.1f; // Velocidad en Y
|
||||
|
||||
// Seleccionar color de la paleta del tema actual (estático o dinámico)
|
||||
Color COLOR;
|
||||
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
||||
// Tema dinámico: usar colores del keyframe actual
|
||||
const DynamicTheme& dyn_theme = dynamic_themes_[current_dynamic_theme_index_];
|
||||
const DynamicThemeKeyframe& current_kf = dyn_theme.keyframes[current_keyframe_index_];
|
||||
int color_index = rand() % current_kf.ball_colors.size();
|
||||
COLOR = current_kf.ball_colors[color_index];
|
||||
} else {
|
||||
// Tema estático
|
||||
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
||||
int color_index = rand() % theme.ball_colors.size();
|
||||
COLOR = theme.ball_colors[color_index];
|
||||
}
|
||||
// Seleccionar color de la paleta del tema actual (delegado a ThemeManager)
|
||||
int random_index = rand();
|
||||
Color COLOR = theme_manager_->getInitialBallColor(random_index);
|
||||
|
||||
// Generar factor de masa aleatorio (0.7 = ligera, 1.3 = pesada)
|
||||
float mass_factor = GRAVITY_MASS_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN);
|
||||
balls_.emplace_back(std::make_unique<Ball>(X, VX, VY, COLOR, texture_, current_screen_width_, current_screen_height_, current_ball_size_, current_gravity_, mass_factor));
|
||||
@@ -1114,77 +1164,6 @@ std::string Engine::gravityDirectionToString(GravityDirection direction) const {
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::renderGradientBackground() {
|
||||
// Crear quad de pantalla completa con degradado
|
||||
SDL_Vertex bg_vertices[4];
|
||||
|
||||
// Obtener colores (con LERP si estamos en transición)
|
||||
float top_r, top_g, top_b, bottom_r, bottom_g, bottom_b;
|
||||
|
||||
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
||||
// Tema dinámico activo: interpolar entre keyframes
|
||||
DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
||||
const DynamicThemeKeyframe& current_kf = theme.keyframes[current_keyframe_index_];
|
||||
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
||||
|
||||
top_r = lerp(current_kf.bg_top_r, target_kf.bg_top_r, dynamic_transition_progress_);
|
||||
top_g = lerp(current_kf.bg_top_g, target_kf.bg_top_g, dynamic_transition_progress_);
|
||||
top_b = lerp(current_kf.bg_top_b, target_kf.bg_top_b, dynamic_transition_progress_);
|
||||
|
||||
bottom_r = lerp(current_kf.bg_bottom_r, target_kf.bg_bottom_r, dynamic_transition_progress_);
|
||||
bottom_g = lerp(current_kf.bg_bottom_g, target_kf.bg_bottom_g, dynamic_transition_progress_);
|
||||
bottom_b = lerp(current_kf.bg_bottom_b, target_kf.bg_bottom_b, dynamic_transition_progress_);
|
||||
} else if (transitioning_) {
|
||||
// Transición estática: interpolar entre tema actual y tema destino
|
||||
ThemeColors& current = themes_[static_cast<int>(current_theme_)];
|
||||
ThemeColors& target = themes_[static_cast<int>(target_theme_)];
|
||||
|
||||
top_r = lerp(current.bg_top_r, target.bg_top_r, transition_progress_);
|
||||
top_g = lerp(current.bg_top_g, target.bg_top_g, transition_progress_);
|
||||
top_b = lerp(current.bg_top_b, target.bg_top_b, transition_progress_);
|
||||
|
||||
bottom_r = lerp(current.bg_bottom_r, target.bg_bottom_r, transition_progress_);
|
||||
bottom_g = lerp(current.bg_bottom_g, target.bg_bottom_g, transition_progress_);
|
||||
bottom_b = lerp(current.bg_bottom_b, target.bg_bottom_b, transition_progress_);
|
||||
} else {
|
||||
// Sin transición: usar tema estático actual directamente
|
||||
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
|
||||
top_r = theme.bg_top_r;
|
||||
top_g = theme.bg_top_g;
|
||||
top_b = theme.bg_top_b;
|
||||
|
||||
bottom_r = theme.bg_bottom_r;
|
||||
bottom_g = theme.bg_bottom_g;
|
||||
bottom_b = theme.bg_bottom_b;
|
||||
}
|
||||
|
||||
// Vértice superior izquierdo
|
||||
bg_vertices[0].position = {0, 0};
|
||||
bg_vertices[0].tex_coord = {0.0f, 0.0f};
|
||||
bg_vertices[0].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice superior derecho
|
||||
bg_vertices[1].position = {static_cast<float>(current_screen_width_), 0};
|
||||
bg_vertices[1].tex_coord = {1.0f, 0.0f};
|
||||
bg_vertices[1].color = {top_r, top_g, top_b, 1.0f};
|
||||
|
||||
// Vértice inferior derecho
|
||||
bg_vertices[2].position = {static_cast<float>(current_screen_width_), static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[2].tex_coord = {1.0f, 1.0f};
|
||||
bg_vertices[2].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Vértice inferior izquierdo
|
||||
bg_vertices[3].position = {0, static_cast<float>(current_screen_height_)};
|
||||
bg_vertices[3].tex_coord = {0.0f, 1.0f};
|
||||
bg_vertices[3].color = {bottom_r, bottom_g, bottom_b, 1.0f};
|
||||
|
||||
// Índices para 2 triángulos
|
||||
int bg_indices[6] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
// Renderizar sin textura (nullptr)
|
||||
SDL_RenderGeometry(renderer_, nullptr, bg_vertices, 4, bg_indices, 6);
|
||||
}
|
||||
|
||||
void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale) {
|
||||
int vertex_index = static_cast<int>(batch_vertices_.size());
|
||||
|
||||
@@ -1312,473 +1291,6 @@ void Engine::zoomOut() {
|
||||
setWindowZoom(current_window_zoom_ - 1);
|
||||
}
|
||||
|
||||
void Engine::initializeThemes() {
|
||||
// SUNSET: Naranjas, rojos, amarillos, rosas (8 colores)
|
||||
themes_[0] = {
|
||||
"SUNSET",
|
||||
"ATARDECER", // Nombres (inglés, español)
|
||||
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)
|
||||
{{255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147}, {255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60}}};
|
||||
|
||||
// OCEAN: Azules, turquesas, blancos (8 colores)
|
||||
themes_[1] = {
|
||||
"OCEAN",
|
||||
"OCEANO", // Nombres (inglés, español)
|
||||
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)
|
||||
{{0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230}, {70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208}}};
|
||||
|
||||
// NEON: Cian, magenta, verde lima, amarillo vibrante (8 colores)
|
||||
themes_[2] = {
|
||||
"NEON",
|
||||
"NEON", // Nombres (inglés, español)
|
||||
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)
|
||||
{{0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0}, {255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0}}};
|
||||
|
||||
// FOREST: Verdes, marrones, amarillos otoño (8 colores)
|
||||
themes_[3] = {
|
||||
"FOREST",
|
||||
"BOSQUE", // Nombres (inglés, español)
|
||||
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)
|
||||
{{34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0}, {210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50}}};
|
||||
|
||||
// RGB: Círculo cromático con 24 puntos (cada 15°) - Ultra precisión matemática
|
||||
themes_[4] = {
|
||||
"RGB",
|
||||
"RGB", // Nombres (inglés, español)
|
||||
100,
|
||||
100,
|
||||
100, // Color texto: gris oscuro (contraste con fondo blanco)
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f, // Fondo superior (blanco puro)
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f, // Fondo inferior (blanco puro) - sin degradado
|
||||
{
|
||||
{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
|
||||
}};
|
||||
|
||||
// MONOCHROME: Fondo negro degradado, sprites blancos monocromáticos (8 tonos grises)
|
||||
themes_[5] = {
|
||||
"MONOCHROME",
|
||||
"MONOCROMO", // Nombres (inglés, español)
|
||||
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)
|
||||
{
|
||||
{255, 255, 255}, // Blanco puro - todas las pelotas del mismo color
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255}}};
|
||||
|
||||
// LAVENDER: Degradado violeta oscuro → azul medianoche, pelotas amarillo dorado monocromático
|
||||
themes_[6] = {
|
||||
"LAVENDER",
|
||||
"LAVANDA", // Nombres (inglés, español)
|
||||
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)
|
||||
{
|
||||
{255, 215, 0}, // Amarillo dorado - todas las pelotas del mismo color
|
||||
{255, 215, 0},
|
||||
{255, 215, 0},
|
||||
{255, 215, 0},
|
||||
{255, 215, 0},
|
||||
{255, 215, 0},
|
||||
{255, 215, 0},
|
||||
{255, 215, 0}}};
|
||||
}
|
||||
|
||||
void Engine::initializeDynamicThemes() {
|
||||
// ========================================================================
|
||||
// DYNAMIC_1: "SUNRISE" (Amanecer) - Noche → Alba → Día → Loop
|
||||
// ========================================================================
|
||||
dynamic_themes_[0] = {
|
||||
"SUNRISE",
|
||||
"AMANECER",
|
||||
255,
|
||||
200,
|
||||
100, // Color texto: amarillo cálido
|
||||
{
|
||||
// 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
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160}, {95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
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
|
||||
{{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90}, {255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}}, // Pelotas naranjas
|
||||
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
|
||||
{{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190}, {255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}}, // Pelotas amarillas brillantes
|
||||
3.0f // 3 segundos para llegar aquí
|
||||
},
|
||||
// Keyframe 3: Vuelta a noche (para loop)
|
||||
{
|
||||
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
|
||||
{{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160}, {95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}}, // Pelotas azules tenues
|
||||
5.0f // 5 segundos para volver a noche
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// DYNAMIC_2: "OCEAN WAVES" (Olas Oceánicas) - Azul oscuro ↔ Turquesa
|
||||
// ========================================================================
|
||||
dynamic_themes_[1] = {
|
||||
"OCEAN WAVES",
|
||||
"OLAS OCEANICAS",
|
||||
100,
|
||||
220,
|
||||
255, // Color texto: cian claro
|
||||
{
|
||||
// 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
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175}, {65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
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
|
||||
{{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250}, {105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}}, // Pelotas turquesa brillante
|
||||
4.0f // 4 segundos para llegar
|
||||
},
|
||||
// Keyframe 2: Vuelta a profundidad (para loop)
|
||||
{
|
||||
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
|
||||
{{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175}, {65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}}, // Pelotas azul oscuro
|
||||
4.0f // 4 segundos para volver
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// DYNAMIC_3: "NEON PULSE" (Pulso Neón) - Negro → Neón brillante (rápido)
|
||||
// ========================================================================
|
||||
dynamic_themes_[2] = {
|
||||
"NEON PULSE",
|
||||
"PULSO NEON",
|
||||
255,
|
||||
60,
|
||||
255, // Color texto: magenta brillante
|
||||
{
|
||||
// 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
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48}, {42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
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
|
||||
{{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255}, {50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}}, // Pelotas neón vibrante
|
||||
1.5f // 1.5 segundos para encender (rápido)
|
||||
},
|
||||
// Keyframe 2: Vuelta a apagado (para loop ping-pong)
|
||||
{
|
||||
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
|
||||
{{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48}, {42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}}, // Pelotas grises muy oscuras
|
||||
1.5f // 1.5 segundos para apagar
|
||||
}},
|
||||
true // Loop = true
|
||||
};
|
||||
}
|
||||
|
||||
void Engine::startThemeTransition(ColorTheme new_theme) {
|
||||
if (new_theme == current_theme_) return; // Ya estamos en ese tema
|
||||
|
||||
// Si venimos de tema dinámico, hacer transición instantánea (sin LERP)
|
||||
// porque current_theme_ apunta a DYNAMIC_X (índice >= 7) y no existe en themes_[]
|
||||
if (dynamic_theme_active_) {
|
||||
dynamic_theme_active_ = false;
|
||||
current_dynamic_theme_index_ = -1;
|
||||
|
||||
// Cambio instantáneo: sin transición LERP
|
||||
current_theme_ = new_theme;
|
||||
transitioning_ = false;
|
||||
|
||||
// Actualizar colores de pelotas al tema final
|
||||
const ThemeColors& theme = themes_[static_cast<int>(new_theme)];
|
||||
for (size_t i = 0; i < balls_.size(); i++) {
|
||||
size_t color_index = i % theme.ball_colors.size();
|
||||
balls_[i]->setColor(theme.ball_colors[color_index]);
|
||||
}
|
||||
|
||||
// Mostrar nombre del tema
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme.name_es;
|
||||
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Transición normal entre temas estáticos
|
||||
target_theme_ = new_theme;
|
||||
transitioning_ = true;
|
||||
transition_progress_ = 0.0f;
|
||||
|
||||
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
ThemeColors& theme = themes_[static_cast<int>(new_theme)];
|
||||
text_ = theme.name_es;
|
||||
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateDynamicTheme() {
|
||||
if (!dynamic_theme_active_ || current_dynamic_theme_index_ < 0) return;
|
||||
if (dynamic_theme_paused_) return; // Pausado con Shift+D
|
||||
|
||||
DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
||||
|
||||
// Obtener keyframe destino para calcular duración
|
||||
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
||||
|
||||
// Avanzar progreso de transición
|
||||
dynamic_transition_progress_ += delta_time_ / target_kf.duration;
|
||||
|
||||
if (dynamic_transition_progress_ >= 1.0f) {
|
||||
// Transición completa: avanzar al siguiente keyframe
|
||||
dynamic_transition_progress_ = 0.0f;
|
||||
current_keyframe_index_ = target_keyframe_index_;
|
||||
|
||||
// Calcular siguiente keyframe destino
|
||||
target_keyframe_index_++;
|
||||
if (target_keyframe_index_ >= theme.keyframes.size()) {
|
||||
if (theme.loop) {
|
||||
target_keyframe_index_ = 0; // Volver al inicio
|
||||
} else {
|
||||
target_keyframe_index_ = theme.keyframes.size() - 1; // Quedarse en el último
|
||||
dynamic_theme_active_ = false; // Detener animación
|
||||
}
|
||||
}
|
||||
|
||||
// NOTA: No se actualiza Ball::color_ aquí porque getInterpolatedColor()
|
||||
// calcula el color directamente desde los keyframes cada frame.
|
||||
// Cuando progress=1.0, getInterpolatedColor() devuelve exactamente el color destino.
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::activateDynamicTheme(int index) {
|
||||
if (index < 0 || index >= 3) return; // Validar índice
|
||||
|
||||
// Desactivar transición estática si estaba activa
|
||||
if (transitioning_) {
|
||||
transitioning_ = false;
|
||||
current_theme_ = target_theme_;
|
||||
}
|
||||
|
||||
// Activar tema dinámico
|
||||
dynamic_theme_active_ = true;
|
||||
current_dynamic_theme_index_ = index;
|
||||
current_keyframe_index_ = 0;
|
||||
target_keyframe_index_ = 1;
|
||||
dynamic_transition_progress_ = 0.0f;
|
||||
dynamic_theme_paused_ = false;
|
||||
|
||||
// NOTA: No actualizamos current_theme_ porque cuando dynamic_theme_active_=true,
|
||||
// todo el código usa current_dynamic_theme_index_ en su lugar
|
||||
|
||||
// Establecer colores iniciales del keyframe 0
|
||||
DynamicTheme& theme = dynamic_themes_[index];
|
||||
const DynamicThemeKeyframe& initial_kf = theme.keyframes[0];
|
||||
|
||||
for (size_t i = 0; i < balls_.size(); i++) {
|
||||
size_t color_index = i % initial_kf.ball_colors.size();
|
||||
balls_[i]->setColor(initial_kf.ball_colors[color_index]);
|
||||
}
|
||||
|
||||
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme.name_es;
|
||||
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::pauseDynamicTheme() {
|
||||
if (!dynamic_theme_active_) return; // Solo funciona si hay tema dinámico activo
|
||||
|
||||
dynamic_theme_paused_ = !dynamic_theme_paused_;
|
||||
|
||||
// Mostrar estado de pausa (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = dynamic_theme_paused_ ? "PAUSADO" : "REANUDADO";
|
||||
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
|
||||
Color Engine::getInterpolatedColor(size_t ball_index) const {
|
||||
if (dynamic_theme_active_ && current_dynamic_theme_index_ >= 0) {
|
||||
// Tema dinámico activo: interpolar entre keyframes
|
||||
const DynamicTheme& theme = dynamic_themes_[current_dynamic_theme_index_];
|
||||
const DynamicThemeKeyframe& current_kf = theme.keyframes[current_keyframe_index_];
|
||||
const DynamicThemeKeyframe& target_kf = theme.keyframes[target_keyframe_index_];
|
||||
|
||||
// Obtener colores desde keyframes (NO desde Ball::color_)
|
||||
size_t color_index = ball_index % current_kf.ball_colors.size();
|
||||
Color current_color = current_kf.ball_colors[color_index]; // Color del keyframe actual
|
||||
Color target_color = target_kf.ball_colors[color_index]; // Color del keyframe destino
|
||||
|
||||
// Interpolar RGB entre keyframes
|
||||
return {
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), dynamic_transition_progress_)),
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), dynamic_transition_progress_)),
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), dynamic_transition_progress_))};
|
||||
} else if (transitioning_) {
|
||||
// Transición estática: interpolar entre tema actual y tema destino
|
||||
Color current_color = balls_[ball_index]->getColor();
|
||||
|
||||
// Obtener el color destino (mismo índice de color en el tema destino)
|
||||
const ThemeColors& target_theme = themes_[static_cast<int>(target_theme_)];
|
||||
size_t color_index = ball_index % target_theme.ball_colors.size();
|
||||
Color target_color = target_theme.ball_colors[color_index];
|
||||
|
||||
// Interpolar RGB
|
||||
return {
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), transition_progress_)),
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), transition_progress_)),
|
||||
static_cast<int>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), transition_progress_))};
|
||||
} else {
|
||||
// Sin transición: devolver color actual
|
||||
return balls_[ball_index]->getColor();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO)
|
||||
// ============================================================================
|
||||
@@ -1998,7 +1510,7 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
if (is_lite) {
|
||||
// DEMO LITE: Verificar condiciones para salto a Logo Mode
|
||||
if (static_cast<int>(balls_.size()) >= LOGO_MODE_MIN_BALLS &&
|
||||
current_theme_ == ColorTheme::MONOCHROME) {
|
||||
theme_manager_->getCurrentThemeIndex() == 5) { // MONOCHROME
|
||||
// 10% probabilidad de saltar a Logo Mode
|
||||
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE) {
|
||||
enterLogoMode(true); // Entrar desde DEMO
|
||||
@@ -2115,8 +1627,9 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
// Cambiar tema (15%)
|
||||
accumulated_weight += DEMO_WEIGHT_THEME;
|
||||
if (random_value < accumulated_weight) {
|
||||
ColorTheme new_theme = static_cast<ColorTheme>(rand() % 6);
|
||||
startThemeTransition(new_theme);
|
||||
// Elegir entre TODOS los 10 temas (estáticos + dinámicos)
|
||||
int random_theme_index = rand() % 10;
|
||||
theme_manager_->switchToTheme(random_theme_index);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2204,9 +1717,9 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
|
||||
scenario_ = valid_scenarios[rand() % 5];
|
||||
initBalls(scenario_);
|
||||
|
||||
// 2. Tema
|
||||
ColorTheme new_theme = static_cast<ColorTheme>(rand() % 6);
|
||||
startThemeTransition(new_theme);
|
||||
// 2. Tema (elegir entre TODOS los 10 temas)
|
||||
int random_theme_index = rand() % 10;
|
||||
theme_manager_->switchToTheme(random_theme_index);
|
||||
|
||||
// 3. Sprite
|
||||
if (rand() % 2 == 0) {
|
||||
@@ -2272,7 +1785,7 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
}
|
||||
|
||||
// Guardar estado previo (para restaurar al salir)
|
||||
logo_previous_theme_ = current_theme_;
|
||||
logo_previous_theme_ = theme_manager_->getCurrentThemeIndex();
|
||||
logo_previous_texture_index_ = current_texture_index_;
|
||||
logo_previous_shape_scale_ = shape_scale_factor_;
|
||||
|
||||
@@ -2300,7 +1813,7 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
}
|
||||
|
||||
// Cambiar a tema MONOCHROME
|
||||
startThemeTransition(ColorTheme::MONOCHROME);
|
||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
||||
|
||||
// Establecer escala a 120%
|
||||
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
|
||||
@@ -2333,7 +1846,7 @@ void Engine::exitLogoMode(bool return_to_demo) {
|
||||
if (current_app_mode_ != AppMode::LOGO) return;
|
||||
|
||||
// Restaurar estado previo
|
||||
startThemeTransition(logo_previous_theme_);
|
||||
theme_manager_->switchToTheme(logo_previous_theme_);
|
||||
|
||||
if (logo_previous_texture_index_ != current_texture_index_ &&
|
||||
logo_previous_texture_index_ < textures_.size()) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
|
||||
#include "external/texture.h" // for Texture
|
||||
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
|
||||
#include "theme_manager.h" // for ThemeManager
|
||||
|
||||
// Modos de aplicación mutuamente excluyentes
|
||||
enum class AppMode {
|
||||
@@ -79,34 +80,8 @@ class Engine {
|
||||
int current_screen_width_ = DEFAULT_SCREEN_WIDTH;
|
||||
int current_screen_height_ = DEFAULT_SCREEN_HEIGHT;
|
||||
|
||||
// Sistema de temas
|
||||
ColorTheme current_theme_ = ColorTheme::SUNSET;
|
||||
ColorTheme target_theme_ = ColorTheme::SUNSET; // Tema destino para transición
|
||||
bool transitioning_ = false; // ¿Estamos en transición?
|
||||
float transition_progress_ = 0.0f; // Progreso de 0.0 a 1.0
|
||||
float transition_duration_ = 0.5f; // Duración en segundos
|
||||
|
||||
// Estructura de tema de colores
|
||||
struct ThemeColors {
|
||||
const char* name_en; // Nombre en inglés (para debug)
|
||||
const char* name_es; // Nombre en español (para display)
|
||||
int text_color_r, text_color_g, text_color_b; // Color del texto del tema
|
||||
float bg_top_r, bg_top_g, bg_top_b;
|
||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||
std::vector<Color> ball_colors;
|
||||
};
|
||||
|
||||
// Temas de colores definidos
|
||||
ThemeColors themes_[7]; // 7 temas: SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER
|
||||
|
||||
// Sistema de Temas Dinámicos (animados)
|
||||
DynamicTheme dynamic_themes_[3]; // 3 temas dinámicos predefinidos
|
||||
bool dynamic_theme_active_ = false; // ¿Tema dinámico activo?
|
||||
int current_dynamic_theme_index_ = -1; // Índice del tema dinámico actual (-1 = ninguno)
|
||||
size_t current_keyframe_index_ = 0; // Keyframe actual
|
||||
size_t target_keyframe_index_ = 1; // Próximo keyframe
|
||||
float dynamic_transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia próximo keyframe
|
||||
bool dynamic_theme_paused_ = false; // Pausa manual con Shift+D
|
||||
// Sistema de temas (delegado a ThemeManager)
|
||||
std::unique_ptr<ThemeManager> theme_manager_;
|
||||
|
||||
// Sistema de Figuras 3D (polimórfico)
|
||||
SimulationMode current_mode_ = SimulationMode::PHYSICS;
|
||||
@@ -135,7 +110,7 @@ class Engine {
|
||||
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
|
||||
|
||||
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
||||
ColorTheme logo_previous_theme_ = ColorTheme::SUNSET;
|
||||
int logo_previous_theme_ = 0; // Índice de tema (0-9)
|
||||
size_t logo_previous_texture_index_ = 0;
|
||||
float logo_previous_shape_scale_ = 1.0f;
|
||||
|
||||
@@ -163,13 +138,6 @@ class Engine {
|
||||
void toggleRealFullscreen();
|
||||
void toggleIntegerScaling();
|
||||
std::string gravityDirectionToString(GravityDirection direction) const;
|
||||
void initializeThemes();
|
||||
|
||||
// Sistema de Temas Dinámicos
|
||||
void initializeDynamicThemes(); // Inicializar 3 temas dinámicos predefinidos
|
||||
void updateDynamicTheme(); // Actualizar animación de tema dinámico (llamado cada frame)
|
||||
void activateDynamicTheme(int index); // Activar tema dinámico (0-2)
|
||||
void pauseDynamicTheme(); // Toggle pausa de animación (Shift+D)
|
||||
|
||||
// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO)
|
||||
void setState(AppMode new_mode); // Cambiar modo de aplicación (mutuamente excluyente)
|
||||
@@ -185,11 +153,6 @@ class Engine {
|
||||
void enterLogoMode(bool from_demo = false); // Entrar al modo logo (manual o automático)
|
||||
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
|
||||
|
||||
// Sistema de transiciones LERP
|
||||
float lerp(float a, float b, float t) const { return a + (b - a) * t; }
|
||||
Color getInterpolatedColor(size_t ball_index) const; // Obtener color interpolado durante transición
|
||||
void startThemeTransition(ColorTheme new_theme);
|
||||
|
||||
// Sistema de cambio de sprites dinámico
|
||||
void switchTexture(); // Cambia a siguiente textura disponible
|
||||
void updateBallSizes(int old_size, int new_size); // Ajusta posiciones al cambiar tamaño
|
||||
@@ -201,7 +164,6 @@ class Engine {
|
||||
void zoomOut();
|
||||
|
||||
// Rendering
|
||||
void renderGradientBackground();
|
||||
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale = 1.0f);
|
||||
|
||||
// Sistema de Figuras 3D
|
||||
|
||||
320
source/theme_manager.cpp
Normal file
320
source/theme_manager.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#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
|
||||
}
|
||||
|
||||
// Cambiar tema activo
|
||||
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) {
|
||||
// Solo actualizar si el tema actual necesita update (dinámicos)
|
||||
if (themes_[current_theme_index_]->needsUpdate()) {
|
||||
themes_[current_theme_index_]->update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeManager::cycleTheme() {
|
||||
// Ciclar al siguiente tema con wraparound
|
||||
current_theme_index_ = (current_theme_index_ + 1) % static_cast<int>(themes_.size());
|
||||
|
||||
// Si es tema dinámico, reiniciar progreso
|
||||
if (themes_[current_theme_index_]->needsUpdate()) {
|
||||
themes_[current_theme_index_]->resetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// Delegar al tema activo (progress = 0.0f por ahora, PHASE 3 usará LERP externo)
|
||||
return themes_[current_theme_index_]->getBallColor(ball_index, 0.0f);
|
||||
}
|
||||
|
||||
void ThemeManager::getBackgroundColors(float& top_r, float& top_g, float& top_b,
|
||||
float& bottom_r, float& bottom_g, float& bottom_b) const {
|
||||
// Delegar al tema activo (progress = 0.0f por ahora, PHASE 3 usará LERP externo)
|
||||
themes_[current_theme_index_]->getBackgroundColors(0.0f, top_r, top_g, top_b, bottom_r, bottom_g, bottom_b);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 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 {
|
||||
themes_[current_theme_index_]->getTextColor(r, g, b);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
83
source/theme_manager.h
Normal file
83
source/theme_manager.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // for unique_ptr
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "ball.h" // for Ball class
|
||||
#include "defines.h" // for Color, ColorTheme
|
||||
#include "themes/theme.h" // for Theme interface
|
||||
|
||||
/**
|
||||
* ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos)
|
||||
*
|
||||
* PHASE 2 - Sistema Unificado:
|
||||
* - Vector unificado de 10 temas (7 estáticos + 3 dinámicos)
|
||||
* - Índices 0-9 mapeados a ColorTheme enum (SUNSET=0, OCEAN=1, ..., NEON_PULSE=9)
|
||||
* - API simplificada: switchToTheme(0-9) para cualquier tema
|
||||
* - Sin lógica dual (eliminados if(dynamic_theme_active_) scattered)
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Mantener 10 temas polimórficos (StaticTheme / DynamicTheme)
|
||||
* - Actualizar animación de tema activo si es dinámico
|
||||
* - Proporcionar colores interpolados para renderizado
|
||||
* - Preparar para PHASE 3 (LERP universal entre cualquier par de temas)
|
||||
*
|
||||
* Uso desde Engine:
|
||||
* - initialize() al inicio
|
||||
* - update(delta_time) cada frame
|
||||
* - switchToTheme(0-9) para cambiar tema (Numpad 1-0, Tecla B)
|
||||
* - getInterpolatedColor(index) en render loop
|
||||
*/
|
||||
class ThemeManager {
|
||||
public:
|
||||
// Constructor/Destructor
|
||||
ThemeManager() = default;
|
||||
~ThemeManager() = default;
|
||||
|
||||
// Inicialización
|
||||
void initialize(); // Inicializa 10 temas unificados (7 estáticos + 3 dinámicos)
|
||||
|
||||
// Interfaz unificada (PHASE 2)
|
||||
void switchToTheme(int theme_index); // Cambia a tema 0-9 (instantáneo por ahora, LERP en PHASE 3)
|
||||
void update(float delta_time); // Actualiza tema activo (solo si es dinámico)
|
||||
void cycleTheme(); // Cicla al siguiente tema (0→1→...→9→0) - Tecla B
|
||||
void pauseDynamic(); // Toggle pausa de animación (Shift+D, solo dinámicos)
|
||||
|
||||
// Queries de colores (usado en rendering)
|
||||
Color getInterpolatedColor(size_t ball_index) const; // Obtiene color interpolado para pelota
|
||||
void getBackgroundColors(float& top_r, float& top_g, float& top_b,
|
||||
float& bottom_r, float& bottom_g, float& bottom_b) const; // Obtiene colores de fondo degradado
|
||||
|
||||
// Queries de estado (para debug display y lógica)
|
||||
int getCurrentThemeIndex() const { return current_theme_index_; }
|
||||
bool isCurrentThemeDynamic() const;
|
||||
|
||||
// Obtener información de tema actual para debug display
|
||||
const char* getCurrentThemeNameEN() const;
|
||||
const char* getCurrentThemeNameES() const;
|
||||
void getCurrentThemeTextColor(int& r, int& g, int& b) const;
|
||||
|
||||
// Obtener color inicial para nuevas pelotas (usado en initBalls)
|
||||
Color getInitialBallColor(int random_index) const;
|
||||
|
||||
private:
|
||||
// ========================================
|
||||
// DATOS UNIFICADOS (PHASE 2)
|
||||
// ========================================
|
||||
|
||||
// Vector unificado de 10 temas (índices 0-9)
|
||||
// 0-6: Estáticos (SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER)
|
||||
// 7-9: Dinámicos (SUNRISE, OCEAN_WAVES, NEON_PULSE)
|
||||
std::vector<std::unique_ptr<Theme>> themes_;
|
||||
|
||||
// Índice de tema activo actual (0-9)
|
||||
int current_theme_index_ = 0; // Por defecto SUNSET
|
||||
|
||||
// ========================================
|
||||
// MÉTODOS PRIVADOS
|
||||
// ========================================
|
||||
|
||||
// Inicialización
|
||||
void initializeStaticThemes(); // Crea 7 temas estáticos (índices 0-6)
|
||||
void initializeDynamicThemes(); // Crea 3 temas dinámicos (índices 7-9)
|
||||
};
|
||||
122
source/themes/dynamic_theme.cpp
Normal file
122
source/themes/dynamic_theme.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "dynamic_theme.h"
|
||||
#include <algorithm> // for std::min
|
||||
|
||||
DynamicTheme::DynamicTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
std::vector<DynamicThemeKeyframe> keyframes,
|
||||
bool loop)
|
||||
: name_en_(name_en),
|
||||
name_es_(name_es),
|
||||
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
||||
keyframes_(std::move(keyframes)),
|
||||
loop_(loop),
|
||||
current_keyframe_index_(0),
|
||||
target_keyframe_index_(1),
|
||||
transition_progress_(0.0f),
|
||||
paused_(false) {
|
||||
// Validación: mínimo 2 keyframes
|
||||
if (keyframes_.size() < 2) {
|
||||
// Fallback: duplicar primer keyframe si solo hay 1
|
||||
if (keyframes_.size() == 1) {
|
||||
keyframes_.push_back(keyframes_[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Asegurar que target_keyframe_index es válido
|
||||
if (target_keyframe_index_ >= keyframes_.size()) {
|
||||
target_keyframe_index_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicTheme::update(float delta_time) {
|
||||
if (paused_) return; // No actualizar si está pausado
|
||||
|
||||
// Obtener duración del keyframe objetivo
|
||||
float duration = keyframes_[target_keyframe_index_].duration;
|
||||
if (duration <= 0.0f) {
|
||||
duration = 1.0f; // Fallback si duración inválida
|
||||
}
|
||||
|
||||
// Avanzar progreso
|
||||
transition_progress_ += delta_time / duration;
|
||||
|
||||
// Si completamos la transición, avanzar al siguiente keyframe
|
||||
if (transition_progress_ >= 1.0f) {
|
||||
advanceToNextKeyframe();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicTheme::resetProgress() {
|
||||
current_keyframe_index_ = 0;
|
||||
target_keyframe_index_ = 1;
|
||||
if (target_keyframe_index_ >= keyframes_.size()) {
|
||||
target_keyframe_index_ = 0;
|
||||
}
|
||||
transition_progress_ = 0.0f;
|
||||
}
|
||||
|
||||
void DynamicTheme::advanceToNextKeyframe() {
|
||||
// Mover al siguiente keyframe
|
||||
current_keyframe_index_ = target_keyframe_index_;
|
||||
target_keyframe_index_++;
|
||||
|
||||
// Loop: volver al inicio si llegamos al final
|
||||
if (target_keyframe_index_ >= keyframes_.size()) {
|
||||
if (loop_) {
|
||||
target_keyframe_index_ = 0;
|
||||
} else {
|
||||
// Si no hay loop, quedarse en el último keyframe
|
||||
target_keyframe_index_ = keyframes_.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Reiniciar progreso
|
||||
transition_progress_ = 0.0f;
|
||||
}
|
||||
|
||||
Color DynamicTheme::getBallColor(size_t ball_index, float progress) const {
|
||||
// Obtener keyframes actual y objetivo
|
||||
const auto& current_kf = keyframes_[current_keyframe_index_];
|
||||
const auto& target_kf = keyframes_[target_keyframe_index_];
|
||||
|
||||
// Si paletas vacías, retornar blanco
|
||||
if (current_kf.ball_colors.empty() || target_kf.ball_colors.empty()) {
|
||||
return {255, 255, 255};
|
||||
}
|
||||
|
||||
// Obtener colores de ambos keyframes (con wrap)
|
||||
size_t current_palette_size = current_kf.ball_colors.size();
|
||||
size_t target_palette_size = target_kf.ball_colors.size();
|
||||
|
||||
Color c1 = current_kf.ball_colors[ball_index % current_palette_size];
|
||||
Color c2 = target_kf.ball_colors[ball_index % target_palette_size];
|
||||
|
||||
// Interpolar entre ambos colores usando progreso interno
|
||||
// (progress parámetro será usado en PHASE 3 para LERP externo)
|
||||
float t = transition_progress_;
|
||||
return {
|
||||
static_cast<int>(lerp(c1.r, c2.r, t)),
|
||||
static_cast<int>(lerp(c1.g, c2.g, t)),
|
||||
static_cast<int>(lerp(c1.b, c2.b, t))
|
||||
};
|
||||
}
|
||||
|
||||
void DynamicTheme::getBackgroundColors(float progress,
|
||||
float& tr, float& tg, float& tb,
|
||||
float& br, float& bg, float& bb) const {
|
||||
// Obtener keyframes actual y objetivo
|
||||
const auto& current_kf = keyframes_[current_keyframe_index_];
|
||||
const auto& target_kf = keyframes_[target_keyframe_index_];
|
||||
|
||||
// Interpolar colores de fondo usando progreso interno
|
||||
// (progress parámetro será usado en PHASE 3 para LERP externo)
|
||||
float t = transition_progress_;
|
||||
|
||||
tr = lerp(current_kf.bg_top_r, target_kf.bg_top_r, t);
|
||||
tg = lerp(current_kf.bg_top_g, target_kf.bg_top_g, t);
|
||||
tb = lerp(current_kf.bg_top_b, target_kf.bg_top_b, t);
|
||||
|
||||
br = lerp(current_kf.bg_bottom_r, target_kf.bg_bottom_r, t);
|
||||
bg = lerp(current_kf.bg_bottom_g, target_kf.bg_bottom_g, t);
|
||||
bb = lerp(current_kf.bg_bottom_b, target_kf.bg_bottom_b, t);
|
||||
}
|
||||
103
source/themes/dynamic_theme.h
Normal file
103
source/themes/dynamic_theme.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include "theme.h"
|
||||
#include <string>
|
||||
|
||||
// Forward declaration (estructura definida en defines.h)
|
||||
struct DynamicThemeKeyframe;
|
||||
|
||||
/**
|
||||
* DynamicTheme: Tema animado con N keyframes (2+)
|
||||
*
|
||||
* Características:
|
||||
* - Animación continua entre keyframes
|
||||
* - Progreso interno 0.0-1.0 entre keyframe actual y siguiente
|
||||
* - Loop automático (vuelve al primer keyframe al terminar)
|
||||
* - Pausable con Shift+D
|
||||
* - Compatible con LERP externo (PHASE 3) vía parámetro progress
|
||||
*
|
||||
* Uso:
|
||||
* - 3 temas dinámicos: SUNRISE, OCEAN_WAVES, NEON_PULSE
|
||||
* - Indices 7-9 en el array unificado de ThemeManager
|
||||
*/
|
||||
class DynamicTheme : public Theme {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param name_en: Nombre en inglés
|
||||
* @param name_es: Nombre en español
|
||||
* @param text_r, text_g, text_b: Color de texto UI
|
||||
* @param keyframes: Vector de keyframes (mínimo 2)
|
||||
* @param loop: ¿Volver al inicio al terminar? (siempre true en esta app)
|
||||
*/
|
||||
DynamicTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
std::vector<DynamicThemeKeyframe> keyframes,
|
||||
bool loop = true);
|
||||
|
||||
~DynamicTheme() override = default;
|
||||
|
||||
// ========================================
|
||||
// QUERIES BÁSICAS
|
||||
// ========================================
|
||||
|
||||
const char* getNameEN() const override { return name_en_.c_str(); }
|
||||
const char* getNameES() const override { return name_es_.c_str(); }
|
||||
void getTextColor(int& r, int& g, int& b) const override {
|
||||
r = text_r_;
|
||||
g = text_g_;
|
||||
b = text_b_;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CORE: OBTENER COLORES (interpolados)
|
||||
// ========================================
|
||||
|
||||
Color getBallColor(size_t ball_index, float progress) const override;
|
||||
void getBackgroundColors(float progress,
|
||||
float& tr, float& tg, float& tb,
|
||||
float& br, float& bg, float& bb) const override;
|
||||
|
||||
// ========================================
|
||||
// ANIMACIÓN (soporte completo)
|
||||
// ========================================
|
||||
|
||||
void update(float delta_time) override;
|
||||
bool needsUpdate() const override { return true; }
|
||||
float getProgress() const override { return transition_progress_; }
|
||||
void resetProgress() override;
|
||||
|
||||
// ========================================
|
||||
// PAUSA (tecla Shift+D)
|
||||
// ========================================
|
||||
|
||||
bool isPaused() const override { return paused_; }
|
||||
void togglePause() override { paused_ = !paused_; }
|
||||
|
||||
private:
|
||||
// ========================================
|
||||
// DATOS DEL TEMA
|
||||
// ========================================
|
||||
|
||||
std::string name_en_;
|
||||
std::string name_es_;
|
||||
int text_r_, text_g_, text_b_;
|
||||
std::vector<DynamicThemeKeyframe> keyframes_;
|
||||
bool loop_;
|
||||
|
||||
// ========================================
|
||||
// ESTADO DE ANIMACIÓN
|
||||
// ========================================
|
||||
|
||||
size_t current_keyframe_index_ = 0; // Keyframe actual
|
||||
size_t target_keyframe_index_ = 1; // Próximo keyframe
|
||||
float transition_progress_ = 0.0f; // Progreso 0.0-1.0 hacia target
|
||||
bool paused_ = false; // Pausa manual con Shift+D
|
||||
|
||||
// ========================================
|
||||
// UTILIDADES PRIVADAS
|
||||
// ========================================
|
||||
|
||||
float lerp(float a, float b, float t) const { return a + (b - a) * t; }
|
||||
void advanceToNextKeyframe(); // Avanza al siguiente keyframe (con loop)
|
||||
};
|
||||
36
source/themes/static_theme.cpp
Normal file
36
source/themes/static_theme.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "static_theme.h"
|
||||
|
||||
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
float bg_top_r, float bg_top_g, float bg_top_b,
|
||||
float bg_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||
std::vector<Color> ball_colors)
|
||||
: name_en_(name_en),
|
||||
name_es_(name_es),
|
||||
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
||||
bg_top_r_(bg_top_r), bg_top_g_(bg_top_g), bg_top_b_(bg_top_b),
|
||||
bg_bottom_r_(bg_bottom_r), bg_bottom_g_(bg_bottom_g), bg_bottom_b_(bg_bottom_b),
|
||||
ball_colors_(std::move(ball_colors)) {
|
||||
}
|
||||
|
||||
Color StaticTheme::getBallColor(size_t ball_index, float progress) const {
|
||||
// Tema estático: siempre retorna color de paleta según índice
|
||||
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
||||
if (ball_colors_.empty()) {
|
||||
return {255, 255, 255}; // Blanco por defecto si paleta vacía
|
||||
}
|
||||
return ball_colors_[ball_index % ball_colors_.size()];
|
||||
}
|
||||
|
||||
void StaticTheme::getBackgroundColors(float progress,
|
||||
float& tr, float& tg, float& tb,
|
||||
float& br, float& bg, float& bb) const {
|
||||
// Tema estático: siempre retorna colores de fondo fijos
|
||||
// (progress se ignora aquí, pero será usado en PHASE 3 para LERP externo)
|
||||
tr = bg_top_r_;
|
||||
tg = bg_top_g_;
|
||||
tb = bg_top_b_;
|
||||
br = bg_bottom_r_;
|
||||
bg = bg_bottom_g_;
|
||||
bb = bg_bottom_b_;
|
||||
}
|
||||
72
source/themes/static_theme.h
Normal file
72
source/themes/static_theme.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "theme.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* StaticTheme: Tema estático con 1 keyframe (sin animación)
|
||||
*
|
||||
* Características:
|
||||
* - Colores fijos (no cambian con el tiempo)
|
||||
* - Sin update() necesario (needsUpdate() retorna false)
|
||||
* - Progress siempre 0.0 (no hay animación interna)
|
||||
* - Compatible con LERP externo (PHASE 3) vía parámetro progress
|
||||
*
|
||||
* Uso:
|
||||
* - 7 temas estáticos: SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER
|
||||
* - Indices 0-6 en el array unificado de ThemeManager
|
||||
*/
|
||||
class StaticTheme : public Theme {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param name_en: Nombre en inglés
|
||||
* @param name_es: Nombre en español
|
||||
* @param text_r, text_g, text_b: Color de texto UI
|
||||
* @param bg_top_r, bg_top_g, bg_top_b: Color superior de fondo
|
||||
* @param bg_bottom_r, bg_bottom_g, bg_bottom_b: Color inferior de fondo
|
||||
* @param ball_colors: Paleta de colores para pelotas
|
||||
*/
|
||||
StaticTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
float bg_top_r, float bg_top_g, float bg_top_b,
|
||||
float bg_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||
std::vector<Color> ball_colors);
|
||||
|
||||
~StaticTheme() override = default;
|
||||
|
||||
// ========================================
|
||||
// QUERIES BÁSICAS
|
||||
// ========================================
|
||||
|
||||
const char* getNameEN() const override { return name_en_.c_str(); }
|
||||
const char* getNameES() const override { return name_es_.c_str(); }
|
||||
void getTextColor(int& r, int& g, int& b) const override {
|
||||
r = text_r_;
|
||||
g = text_g_;
|
||||
b = text_b_;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CORE: OBTENER COLORES
|
||||
// ========================================
|
||||
|
||||
Color getBallColor(size_t ball_index, float progress) const override;
|
||||
void getBackgroundColors(float progress,
|
||||
float& tr, float& tg, float& tb,
|
||||
float& br, float& bg, float& bb) const override;
|
||||
|
||||
// ========================================
|
||||
// ANIMACIÓN (sin soporte - tema estático)
|
||||
// ========================================
|
||||
|
||||
// update(), needsUpdate(), getProgress(), resetProgress() usan defaults de Theme
|
||||
|
||||
private:
|
||||
std::string name_en_;
|
||||
std::string name_es_;
|
||||
int text_r_, text_g_, text_b_;
|
||||
float bg_top_r_, bg_top_g_, bg_top_b_;
|
||||
float bg_bottom_r_, bg_bottom_g_, bg_bottom_b_;
|
||||
std::vector<Color> ball_colors_;
|
||||
};
|
||||
93
source/themes/theme.h
Normal file
93
source/themes/theme.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "../defines.h" // for Color, ThemeKeyframe
|
||||
|
||||
/**
|
||||
* Theme: Interfaz polimórfica para todos los temas (estáticos y dinámicos)
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Proporcionar información básica del tema (nombre, color de texto)
|
||||
* - Generar colores interpolados para pelotas y fondo
|
||||
* - Actualizar estado interno si es animado (solo dinámicos)
|
||||
*
|
||||
* Implementaciones:
|
||||
* - StaticTheme: 1 keyframe, sin animación, colores fijos
|
||||
* - DynamicTheme: N keyframes, animación continua con loop
|
||||
*/
|
||||
class Theme {
|
||||
public:
|
||||
virtual ~Theme() = default;
|
||||
|
||||
// ========================================
|
||||
// QUERIES BÁSICAS (implementar en derivadas)
|
||||
// ========================================
|
||||
|
||||
virtual const char* getNameEN() const = 0;
|
||||
virtual const char* getNameES() const = 0;
|
||||
virtual void getTextColor(int& r, int& g, int& b) const = 0;
|
||||
|
||||
// ========================================
|
||||
// CORE: OBTENER COLORES (polimórfico)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Obtiene color de pelota según índice y progreso de animación
|
||||
* @param ball_index: Índice de pelota (para seleccionar color de paleta)
|
||||
* @param progress: Progreso 0.0-1.0 si hay transición LERP externa (PHASE 3)
|
||||
* @return Color interpolado para esta pelota
|
||||
*/
|
||||
virtual Color getBallColor(size_t ball_index, float progress) const = 0;
|
||||
|
||||
/**
|
||||
* Obtiene colores de fondo degradado (top/bottom)
|
||||
* @param progress: Progreso 0.0-1.0 si hay transición LERP externa (PHASE 3)
|
||||
* @param tr, tg, tb: Color superior (out)
|
||||
* @param br, bg, bb: Color inferior (out)
|
||||
*/
|
||||
virtual void getBackgroundColors(float progress,
|
||||
float& tr, float& tg, float& tb,
|
||||
float& br, float& bg, float& bb) const = 0;
|
||||
|
||||
// ========================================
|
||||
// ANIMACIÓN (solo temas dinámicos)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Actualiza progreso de animación interna (solo dinámicos)
|
||||
* @param delta_time: Tiempo transcurrido desde último frame
|
||||
*/
|
||||
virtual void update(float delta_time) { }
|
||||
|
||||
/**
|
||||
* ¿Este tema necesita update() cada frame?
|
||||
* @return false para estáticos, true para dinámicos
|
||||
*/
|
||||
virtual bool needsUpdate() const { return false; }
|
||||
|
||||
/**
|
||||
* Obtiene progreso actual de animación interna
|
||||
* @return 0.0 para estáticos, 0.0-1.0 para dinámicos
|
||||
*/
|
||||
virtual float getProgress() const { return 0.0f; }
|
||||
|
||||
/**
|
||||
* Reinicia progreso de animación a 0.0 (usado al activar tema)
|
||||
*/
|
||||
virtual void resetProgress() { }
|
||||
|
||||
// ========================================
|
||||
// PAUSA (solo temas dinámicos)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* ¿Está pausado? (solo dinámicos)
|
||||
* @return false para estáticos, true/false para dinámicos
|
||||
*/
|
||||
virtual bool isPaused() const { return false; }
|
||||
|
||||
/**
|
||||
* Toggle pausa de animación (solo dinámicos, tecla Shift+D)
|
||||
*/
|
||||
virtual void togglePause() { }
|
||||
};
|
||||
Reference in New Issue
Block a user