Refactor fase 1: Extraer InputHandler de Engine

Aplicación del Principio de Responsabilidad Única (SRP) - Fase 1/6

## Cambios realizados

### Nuevos archivos
- source/input/input_handler.h - Declaración clase InputHandler
- source/input/input_handler.cpp - Procesamiento eventos SDL (~180 líneas)
- REFACTOR_PLAN.md - Documento de seguimiento del refactor

### Modificaciones en Engine
- **engine.h**: Agregados 24 métodos públicos para InputHandler
- **engine.cpp**:
  - Eliminado handleEvents() (420 líneas)
  - Implementados métodos públicos wrapper (~180 líneas)
  - Renombrados métodos internos con sufijo `Internal`:
    * toggleShapeMode → toggleShapeModeInternal
    * activateShape → activateShapeInternal
    * switchTexture → switchTextureInternal
  - Bucle run() simplificado (5 → 12 líneas)

### Actualización build
- CMakeLists.txt: Agregado source/input/*.cpp a archivos fuente

## Impacto
- **Líneas extraídas**: ~430 del switch gigante de handleEvents()
- **Compilación**:  Exitosa sin errores
- **Funcionalidad**:  100% preservada

## Beneficios
-  Engine desacoplado de eventos SDL
-  InputHandler stateless (fácilmente testeable)
-  Clara separación detección input vs ejecución lógica
-  Preparado para testing unitario de inputs

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-10 11:39:59 +02:00
parent 5f89299444
commit b8d3c60e58
6 changed files with 778 additions and 464 deletions

View File

@@ -213,6 +213,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
srand(static_cast<unsigned>(time(nullptr)));
// Inicializar InputHandler (sin estado)
input_handler_ = std::make_unique<InputHandler>();
// Inicializar ThemeManager PRIMERO (requerido por Notifier)
theme_manager_ = std::make_unique<ThemeManager>();
theme_manager_->initialize();
@@ -230,8 +233,13 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
void Engine::run() {
while (!should_exit_) {
calculateDeltaTime();
// Procesar eventos de entrada (teclado, ratón, ventana)
if (input_handler_->processEvents(*this)) {
should_exit_ = true;
}
update();
handleEvents();
render();
}
}
@@ -311,426 +319,185 @@ void Engine::update() {
theme_manager_->update(delta_time_);
}
void Engine::handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// Procesar eventos de ratón (auto-ocultar cursor)
Mouse::handleEvent(event);
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
// Salir del bucle si se detecta una petición de cierre
if (event.type == SDL_EVENT_QUIT) {
should_exit_ = true;
break;
// Gravedad y física
void Engine::handleGravityToggle() {
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeModeInternal(false); // Desactivar figura sin forzar gravedad ON
showNotificationForAction("Gravedad Off");
} else {
switchBallsGravity(); // Toggle normal en modo física
// Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON)
bool gravity_on = balls_.empty() ? true : (balls_[0]->getGravityForce() != 0.0f);
showNotificationForAction(gravity_on ? "Gravedad On" : "Gravedad Off");
}
}
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
} else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(direction);
showNotificationForAction(notification_text);
}
// Display y depuración
void Engine::toggleDebug() {
show_debug_ = !show_debug_;
}
// Figuras 3D
void Engine::toggleShapeMode() {
toggleShapeModeInternal();
// Mostrar notificación según el modo actual después del toggle
if (current_mode_ == SimulationMode::PHYSICS) {
showNotificationForAction("Modo Física");
} else {
// Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
// Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]);
}
}
void Engine::activateShape(ShapeType type, const char* notification_text) {
activateShapeInternal(type);
showNotificationForAction(notification_text);
}
void Engine::handleShapeScaleChange(bool increase) {
if (current_mode_ == SimulationMode::SHAPE) {
if (increase) {
shape_scale_factor_ += SHAPE_SCALE_STEP;
} else {
shape_scale_factor_ -= SHAPE_SCALE_STEP;
}
clampShapeScale();
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
}
}
// Procesar eventos de teclado
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0) {
switch (event.key.key) {
case SDLK_ESCAPE:
should_exit_ = true;
break;
void Engine::resetShapeScale() {
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
showNotificationForAction("Escala 100%");
}
}
case SDLK_SPACE:
pushBallsAwayFromGravity();
break;
void Engine::toggleDepthZoom() {
if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
showNotificationForAction(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off");
}
}
case SDLK_G:
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
showNotificationForAction("Gravedad Off");
} else {
switchBallsGravity(); // Toggle normal en modo física
// Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON)
bool gravity_on = balls_.empty() ? true : (balls_[0]->getGravityForce() != 0.0f);
showNotificationForAction(gravity_on ? "Gravedad On" : "Gravedad Off");
}
break;
// Temas de colores
void Engine::cycleTheme(bool forward) {
if (forward) {
theme_manager_->cycleTheme();
} else {
theme_manager_->cyclePrevTheme();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
// Controles de dirección de gravedad con teclas de cursor
case SDLK_UP:
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::UP);
showNotificationForAction("Gravedad Arriba");
break;
void Engine::switchThemeByNumpad(int numpad_key) {
// Mapear tecla numpad a índice de tema según página actual
int theme_index = -1;
case SDLK_DOWN:
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::DOWN);
showNotificationForAction("Gravedad Abajo");
break;
case SDLK_LEFT:
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::LEFT);
showNotificationForAction("Gravedad Izquierda");
break;
case SDLK_RIGHT:
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(); // Desactivar figura (activa gravedad automáticamente)
} else {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::RIGHT);
showNotificationForAction("Gravedad Derecha");
break;
case SDLK_V:
toggleVSync();
break;
case SDLK_H:
show_debug_ = !show_debug_;
break;
// Toggle Física ↔ Última Figura (antes era C)
case SDLK_F:
toggleShapeMode();
// Mostrar notificación según el modo actual después del toggle
if (current_mode_ == SimulationMode::PHYSICS) {
showNotificationForAction("Modo Física");
} else {
// Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
// Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]);
}
break;
// Selección directa de figuras 3D
case SDLK_Q:
activateShape(ShapeType::SPHERE);
showNotificationForAction("Esfera");
break;
case SDLK_W:
activateShape(ShapeType::LISSAJOUS);
showNotificationForAction("Lissajous");
break;
case SDLK_E:
activateShape(ShapeType::HELIX);
showNotificationForAction("Hélice");
break;
case SDLK_R:
activateShape(ShapeType::TORUS);
showNotificationForAction("Toroide");
break;
case SDLK_T:
activateShape(ShapeType::CUBE);
showNotificationForAction("Cubo");
break;
case SDLK_Y:
activateShape(ShapeType::CYLINDER);
showNotificationForAction("Cilindro");
break;
case SDLK_U:
activateShape(ShapeType::ICOSAHEDRON);
showNotificationForAction("Icosaedro");
break;
case SDLK_I:
activateShape(ShapeType::ATOM);
showNotificationForAction("Átomo");
break;
case SDLK_O:
activateShape(ShapeType::PNG_SHAPE);
showNotificationForAction("Forma PNG");
break;
// Ciclar temas de color (movido de T a B)
case SDLK_B:
{
// Detectar si Shift está presionado
SDL_Keymod modstate = SDL_GetModState();
if (modstate & SDL_KMOD_SHIFT) {
// Shift+B: Ciclar hacia atrás (tema anterior)
theme_manager_->cyclePrevTheme();
} else {
// B solo: Ciclar hacia adelante (tema siguiente)
theme_manager_->cycleTheme();
}
// Mostrar notificación con el nombre del tema
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
// Temas de colores con teclado numérico (con transición suave)
case SDLK_KP_1:
// Página 0: SUNSET (0), Página 1: OCEAN_WAVES (10)
{
int theme_index = (theme_page_ == 0) ? 0 : 10;
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_2:
// Página 0: OCEAN (1), Página 1: NEON_PULSE (11)
{
int theme_index = (theme_page_ == 0) ? 1 : 11;
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_3:
// Página 0: NEON (2), Página 1: FIRE (12)
{
int theme_index = (theme_page_ == 0) ? 2 : 12;
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_4:
// Página 0: FOREST (3), Página 1: AURORA (13)
{
int theme_index = (theme_page_ == 0) ? 3 : 13;
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_5:
// Página 0: RGB (4), Página 1: VOLCANIC (14)
{
int theme_index = (theme_page_ == 0) ? 4 : 14;
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_6:
// Solo página 0: MONOCHROME (5)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(5); // MONOCHROME
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_7:
// Solo página 0: LAVENDER (6)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(6); // LAVENDER
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_8:
// Solo página 0: CRIMSON (7)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(7); // CRIMSON
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_9:
// Solo página 0: EMERALD (8)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(8); // EMERALD
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
case SDLK_KP_0:
// Solo página 0: SUNRISE (9)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(9); // SUNRISE
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
// Toggle de página de temas (Numpad Enter)
case SDLK_KP_ENTER:
// Alternar entre página 0 y página 1
theme_page_ = (theme_page_ == 0) ? 1 : 0;
showNotificationForAction((theme_page_ == 0) ? "Página 1" : "Página 2");
break;
// Cambio de sprite/textura dinámico
case SDLK_N:
switchTexture();
break;
// Control de escala de figura (solo en modo SHAPE)
case SDLK_KP_PLUS:
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ += SHAPE_SCALE_STEP;
clampShapeScale();
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
}
break;
case SDLK_KP_MINUS:
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ -= SHAPE_SCALE_STEP;
clampShapeScale();
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
}
break;
case SDLK_KP_MULTIPLY:
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
showNotificationForAction("Escala 100%");
}
break;
case SDLK_KP_DIVIDE:
if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
showNotificationForAction(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off");
}
break;
case SDLK_1:
scenario_ = 0;
initBalls(scenario_);
showNotificationForAction("10 Pelotas");
break;
case SDLK_2:
scenario_ = 1;
initBalls(scenario_);
showNotificationForAction("50 Pelotas");
break;
case SDLK_3:
scenario_ = 2;
initBalls(scenario_);
showNotificationForAction("100 Pelotas");
break;
case SDLK_4:
scenario_ = 3;
initBalls(scenario_);
showNotificationForAction("500 Pelotas");
break;
case SDLK_5:
scenario_ = 4;
initBalls(scenario_);
showNotificationForAction("1,000 Pelotas");
break;
case SDLK_6:
scenario_ = 5;
initBalls(scenario_);
showNotificationForAction("5,000 Pelotas");
break;
case SDLK_7:
scenario_ = 6;
initBalls(scenario_);
showNotificationForAction("10,000 Pelotas");
break;
case SDLK_8:
scenario_ = 7;
initBalls(scenario_);
showNotificationForAction("50,000 Pelotas");
break;
// Controles de zoom dinámico (solo si no estamos en fullscreen)
case SDLK_F1:
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
zoomOut();
}
break;
case SDLK_F2:
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
zoomIn();
}
break;
// Control de pantalla completa
case SDLK_F3:
toggleFullscreen();
break;
// Modo real fullscreen (cambia resolución interna)
case SDLK_F4:
toggleRealFullscreen();
break;
// Toggle escalado entero/estirado (solo en fullscreen F3)
case SDLK_F5:
toggleIntegerScaling();
break;
// Toggle Modo DEMO COMPLETO (auto-play) o Pausar tema dinámico (Shift+D)
case SDLK_D:
// Shift+D = Pausar tema dinámico
if (event.key.mod & SDL_KMOD_SHIFT) {
theme_manager_->pauseDynamic();
} else {
// D sin Shift = Toggle DEMO ↔ SANDBOX
if (current_app_mode_ == AppMode::DEMO) {
// Ya estamos en DEMO → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a DEMO
setState(AppMode::DEMO);
randomizeOnDemoStart(false);
showNotificationForAction("MODO DEMO");
}
}
break;
// Toggle Modo DEMO LITE (solo física/figuras)
case SDLK_L:
if (current_app_mode_ == AppMode::DEMO_LITE) {
// Ya estamos en DEMO_LITE → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a DEMO_LITE
setState(AppMode::DEMO_LITE);
randomizeOnDemoStart(true);
showNotificationForAction("MODO DEMO LITE");
}
break;
// Toggle Modo LOGO (easter egg - marca de agua)
case SDLK_K:
if (current_app_mode_ == AppMode::LOGO) {
// Ya estamos en LOGO → volver a SANDBOX
exitLogoMode(false);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a LOGO
enterLogoMode(false);
showNotificationForAction("MODO LOGO");
}
break;
}
if (theme_page_ == 0) {
// Página 0: Temas 0-9 (estáticos + SUNRISE)
if (numpad_key >= 0 && numpad_key <= 9) {
theme_index = (numpad_key == 0) ? 9 : (numpad_key - 1);
}
} else {
// Página 1: Temas 10-14 (dinámicos)
if (numpad_key >= 1 && numpad_key <= 5) {
theme_index = 9 + numpad_key;
}
}
if (theme_index != -1) {
theme_manager_->switchToTheme(theme_index);
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
}
void Engine::toggleThemePage() {
theme_page_ = (theme_page_ == 0) ? 1 : 0;
showNotificationForAction((theme_page_ == 0) ? "Página 1" : "Página 2");
}
void Engine::pauseDynamicTheme() {
theme_manager_->pauseDynamic();
}
// Sprites/Texturas
void Engine::switchTexture() {
switchTextureInternal(true); // Mostrar notificación en modo manual
}
// Escenarios (número de pelotas)
void Engine::changeScenario(int scenario_id, const char* notification_text) {
scenario_ = scenario_id;
initBalls(scenario_);
showNotificationForAction(notification_text);
}
// Zoom y fullscreen
void Engine::handleZoomIn() {
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
zoomIn();
}
}
void Engine::handleZoomOut() {
if (!fullscreen_enabled_ && !real_fullscreen_enabled_) {
zoomOut();
}
}
// Modos de aplicación (DEMO/LOGO)
void Engine::toggleDemoMode() {
if (current_app_mode_ == AppMode::DEMO) {
// Ya estamos en DEMO → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a DEMO
setState(AppMode::DEMO);
randomizeOnDemoStart(false);
showNotificationForAction("MODO DEMO");
}
}
void Engine::toggleDemoLiteMode() {
if (current_app_mode_ == AppMode::DEMO_LITE) {
// Ya estamos en DEMO_LITE → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a DEMO_LITE
setState(AppMode::DEMO_LITE);
randomizeOnDemoStart(true);
showNotificationForAction("MODO DEMO LITE");
}
}
void Engine::toggleLogoMode() {
if (current_app_mode_ == AppMode::LOGO) {
// Ya estamos en LOGO → volver a SANDBOX
exitLogoMode(false);
showNotificationForAction("MODO SANDBOX");
} else {
// Estamos en otro modo → ir a LOGO
enterLogoMode(false);
showNotificationForAction("MODO LOGO");
}
}
@@ -1556,9 +1323,9 @@ void Engine::updateDemoMode() {
// Ya estábamos esperando flips, y se disparó el trigger
// → Hacer el cambio SHAPE → PHYSICS ahora (durante el flip)
if (action < 50) {
toggleShapeMode(true); // Con gravedad ON
toggleShapeModeInternal(true); // Con gravedad ON
} else {
toggleShapeMode(false); // Con gravedad OFF
toggleShapeModeInternal(false); // Con gravedad OFF
}
// Resetear variables de espera de flips
@@ -1590,10 +1357,10 @@ void Engine::updateDemoMode() {
// CAMINO A (50%): Cambio inmediato
if (action < 50) {
// 50%: SHAPE → PHYSICS con gravedad ON (caída dramática)
toggleShapeMode(true);
toggleShapeModeInternal(true);
} else {
// 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer)
toggleShapeMode(false);
toggleShapeModeInternal(false);
}
// Resetear variables de espera de flips al cambiar a PHYSICS
@@ -1609,7 +1376,7 @@ void Engine::updateDemoMode() {
// Logo animado (PHYSICS) → 3 opciones posibles
if (action < 60) {
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
toggleShapeMode(false);
toggleShapeModeInternal(false);
// Resetear variables de espera de flips al volver a SHAPE
logo_waiting_for_flip_ = false;
@@ -1711,14 +1478,14 @@ void Engine::performDemoAction(bool is_lite) {
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
activateShapeInternal(shapes[shape_index]);
return;
}
// Toggle física ↔ figura (20%)
accumulated_weight += DEMO_LITE_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
toggleShapeMode(false); // NO forzar gravedad al salir
toggleShapeModeInternal(false); // NO forzar gravedad al salir
return;
}
@@ -1754,14 +1521,14 @@ void Engine::performDemoAction(bool is_lite) {
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
activateShapeInternal(shapes[shape_index]);
return;
}
// Toggle física ↔ figura (12%)
accumulated_weight += DEMO_WEIGHT_TOGGLE_PHYSICS;
if (random_value < accumulated_weight) {
toggleShapeMode(false); // NO forzar gravedad al salir
toggleShapeModeInternal(false); // NO forzar gravedad al salir
return;
}
@@ -1830,7 +1597,7 @@ void Engine::performDemoAction(bool is_lite) {
// Cambiar sprite (2%)
accumulated_weight += DEMO_WEIGHT_SPRITE;
if (random_value < accumulated_weight) {
switchTexture(false); // Suprimir notificación en modo automático
switchTextureInternal(false); // Suprimir notificación en modo automático
return;
}
}
@@ -1844,7 +1611,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
activateShapeInternal(shapes[rand() % 8]);
}
if (is_lite) {
@@ -1853,12 +1620,12 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
if (rand() % 2 == 0) {
// Modo física
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false); // Salir a física sin forzar gravedad
toggleShapeModeInternal(false); // Salir a física sin forzar gravedad
}
} else {
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
activateShapeInternal(shapes[rand() % 8]);
}
// Randomizar gravedad: dirección + ON/OFF
@@ -1882,19 +1649,19 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
// 3. Sprite
if (rand() % 2 == 0) {
switchTexture(false); // Suprimir notificación al activar modo DEMO
switchTextureInternal(false); // Suprimir notificación al activar modo DEMO
}
// 4. Física o Figura
if (rand() % 2 == 0) {
// Modo física
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeMode(false);
toggleShapeModeInternal(false);
}
} else {
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
activateShapeInternal(shapes[rand() % 8]);
// 5. Profundidad (solo si estamos en figura)
if (rand() % 2 == 0) {
@@ -1981,7 +1748,7 @@ void Engine::enterLogoMode(bool from_demo) {
clampShapeScale();
// Activar PNG_SHAPE (el logo)
activateShape(ShapeType::PNG_SHAPE);
activateShapeInternal(ShapeType::PNG_SHAPE);
// Configurar PNG_SHAPE en modo LOGO (flip intervals más largos)
if (active_shape_) {
@@ -2053,20 +1820,12 @@ void Engine::exitLogoMode(bool return_to_demo) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
activateShapeInternal(shapes[rand() % 8]);
}
}
}
// Toggle manual del Modo Logo (tecla K)
void Engine::toggleLogoMode() {
if (current_app_mode_ == AppMode::LOGO) {
exitLogoMode(false); // Salir y volver a MANUAL
} else {
enterLogoMode(false); // Entrar manualmente
}
}
// Sistema de cambio de sprites dinámico
void Engine::updateBallSizes(int old_size, int new_size) {
float delta_size = static_cast<float>(new_size - old_size);
@@ -2111,7 +1870,7 @@ void Engine::updateBallSizes(int old_size, int new_size) {
}
}
void Engine::switchTexture(bool show_notification) {
void Engine::switchTextureInternal(bool show_notification) {
if (textures_.empty()) return;
// Guardar tamaño antiguo
@@ -2142,10 +1901,10 @@ void Engine::switchTexture(bool show_notification) {
}
// Sistema de Figuras 3D - Alternar entre modo física y última figura (Toggle con tecla F)
void Engine::toggleShapeMode(bool force_gravity_on_exit) {
void Engine::toggleShapeModeInternal(bool force_gravity_on_exit) {
if (current_mode_ == SimulationMode::PHYSICS) {
// Cambiar a modo figura (usar última figura seleccionada)
activateShape(last_shape_type_);
activateShapeInternal(last_shape_type_);
// Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO
if (current_app_mode_ == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) {
@@ -2189,7 +1948,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
}
// Activar figura específica (llamado por teclas Q/W/E/R/Y/U/I o por toggleShapeMode)
void Engine::activateShape(ShapeType type) {
void Engine::activateShapeInternal(ShapeType type) {
// Guardar como última figura seleccionada
last_shape_type_ = type;
current_shape_type_ = type;