Refactor: Sistema de modos y notificaciones mejorado

Cambios principales:
- Renombrado AppMode::MANUAL → AppMode::SANDBOX (nomenclatura más clara)
- Notificaciones ahora funcionan en TODAS las transiciones de modo
- Lógica de teclas D/L/K simplificada: toggle exclusivo modo ↔ SANDBOX
- Mensajes simplificados: "MODO DEMO", "MODO SANDBOX", etc. (sin ON/OFF)
- Eliminado check restrictivo en showNotificationForAction()

Comportamiento nuevo:
- Tecla D: Toggle DEMO ↔ SANDBOX
- Tecla L: Toggle DEMO_LITE ↔ SANDBOX
- Tecla K: Toggle LOGO ↔ SANDBOX
- Cada tecla activa su modo o vuelve a SANDBOX si ya está activo
- Notificaciones visibles tanto al activar como desactivar modos

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-10 07:44:57 +02:00
parent 0d1608712b
commit 10a4234d49
2 changed files with 115 additions and 149 deletions

View File

@@ -329,8 +329,12 @@ void Engine::handleEvents() {
// 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("Gravity 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 ? "Gravity On" : "Gravity Off");
}
break;
@@ -343,6 +347,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::UP);
showNotificationForAction("Gravity Up");
break;
case SDLK_DOWN:
@@ -353,6 +358,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::DOWN);
showNotificationForAction("Gravity Down");
break;
case SDLK_LEFT:
@@ -363,6 +369,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::LEFT);
showNotificationForAction("Gravity Left");
break;
case SDLK_RIGHT:
@@ -373,6 +380,7 @@ void Engine::handleEvents() {
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
changeGravityDirection(GravityDirection::RIGHT);
showNotificationForAction("Gravity Right");
break;
case SDLK_V:
@@ -386,43 +394,60 @@ void Engine::handleEvents() {
// 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("Physics Mode");
} else {
// Mostrar nombre de la figura actual
const char* shape_names[] = {"Sphere", "Lissajous", "Helix", "Torus", "Cube", "Cylinder", "Icosahedron", "Atom", "PNG Shape"};
showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]);
}
break;
// Selección directa de figuras 3D
case SDLK_Q:
activateShape(ShapeType::SPHERE);
showNotificationForAction("Sphere");
break;
case SDLK_W:
activateShape(ShapeType::LISSAJOUS);
showNotificationForAction("Lissajous");
break;
case SDLK_E:
activateShape(ShapeType::HELIX);
showNotificationForAction("Helix");
break;
case SDLK_R:
activateShape(ShapeType::TORUS);
showNotificationForAction("Torus");
break;
case SDLK_T:
activateShape(ShapeType::CUBE);
showNotificationForAction("Cube");
break;
case SDLK_Y:
activateShape(ShapeType::CYLINDER);
showNotificationForAction("Cylinder");
break;
case SDLK_U:
activateShape(ShapeType::ICOSAHEDRON);
showNotificationForAction("Icosahedron");
break;
case SDLK_I:
activateShape(ShapeType::ATOM);
showNotificationForAction("Atom");
break;
case SDLK_O:
activateShape(ShapeType::PNG_SHAPE);
showNotificationForAction("PNG Shape");
break;
// Ciclar temas de color (movido de T a B)
@@ -438,13 +463,8 @@ void Engine::handleEvents() {
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_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
// Mostrar notificación con el nombre del tema
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -454,12 +474,7 @@ void Engine::handleEvents() {
{
int theme_index = (theme_page_ == 0) ? 0 : 10;
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -468,12 +483,7 @@ void Engine::handleEvents() {
{
int theme_index = (theme_page_ == 0) ? 1 : 11;
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -482,12 +492,7 @@ void Engine::handleEvents() {
{
int theme_index = (theme_page_ == 0) ? 2 : 12;
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -496,12 +501,7 @@ void Engine::handleEvents() {
{
int theme_index = (theme_page_ == 0) ? 3 : 13;
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -510,12 +510,7 @@ void Engine::handleEvents() {
{
int theme_index = (theme_page_ == 0) ? 4 : 14;
theme_manager_->switchToTheme(theme_index);
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -523,12 +518,7 @@ void Engine::handleEvents() {
// Solo página 0: MONOCHROME (5)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(5); // MONOCHROME
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -536,12 +526,7 @@ void Engine::handleEvents() {
// Solo página 0: LAVENDER (6)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(6); // LAVENDER
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -549,12 +534,7 @@ void Engine::handleEvents() {
// Solo página 0: CRIMSON (7)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(7); // CRIMSON
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -562,12 +542,7 @@ void Engine::handleEvents() {
// Solo página 0: EMERALD (8)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(8); // EMERALD
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -575,12 +550,7 @@ void Engine::handleEvents() {
// Solo página 0: SUNRISE (9)
if (theme_page_ == 0) {
theme_manager_->switchToTheme(9); // SUNRISE
if (current_app_mode_ == AppMode::MANUAL) {
text_ = theme_manager_->getCurrentThemeNameES();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
}
break;
@@ -588,14 +558,7 @@ void Engine::handleEvents() {
case SDLK_KP_ENTER:
// Alternar entre página 0 y página 1
theme_page_ = (theme_page_ == 0) ? 1 : 0;
// Mostrar feedback visual (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) {
text_ = (theme_page_ == 0) ? "Página 1" : "Página 2";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
}
showNotificationForAction((theme_page_ == 0) ? "Página 1" : "Página 2");
break;
// Cambio de sprite/textura dinámico
@@ -608,11 +571,7 @@ void Engine::handleEvents() {
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ += SHAPE_SCALE_STEP;
clampShapeScale();
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
}
break;
@@ -620,74 +579,70 @@ void Engine::handleEvents() {
if (current_mode_ == SimulationMode::SHAPE) {
shape_scale_factor_ -= SHAPE_SCALE_STEP;
clampShapeScale();
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
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;
text_ = "Escala reiniciada (100%)";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
showNotificationForAction("Escala 100%");
}
break;
case SDLK_KP_DIVIDE:
if (current_mode_ == SimulationMode::SHAPE) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
text_ = depth_zoom_enabled_ ? "Zoom profundidad: On" : "Zoom profundidad: Off";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
showNotificationForAction(depth_zoom_enabled_ ? "Depth Zoom On" : "Depth Zoom 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)
@@ -724,22 +679,16 @@ void Engine::handleEvents() {
if (event.key.mod & SDL_KMOD_SHIFT) {
theme_manager_->pauseDynamic();
} else {
// D sin Shift = Toggle modo DEMO
// D sin Shift = Toggle DEMO ↔ SANDBOX
if (current_app_mode_ == AppMode::DEMO) {
// Desactivar DEMO → MANUAL
setState(AppMode::MANUAL);
text_ = "Modo Demo: Off";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
// Ya estamos en DEMO → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Activar DEMO (desde cualquier otro modo)
// Estamos en otro modo → ir a DEMO
setState(AppMode::DEMO);
randomizeOnDemoStart(false);
text_ = "Modo Demo: On";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
showNotificationForAction("MODO DEMO");
}
}
break;
@@ -747,35 +696,28 @@ void Engine::handleEvents() {
// Toggle Modo DEMO LITE (solo física/figuras)
case SDLK_L:
if (current_app_mode_ == AppMode::DEMO_LITE) {
// Desactivar DEMO_LITE → MANUAL
setState(AppMode::MANUAL);
text_ = "Modo Demo Lite: Off";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
// Ya estamos en DEMO_LITE → volver a SANDBOX
setState(AppMode::SANDBOX);
showNotificationForAction("MODO SANDBOX");
} else {
// Activar DEMO_LITE (desde cualquier otro modo)
// Estamos en otro modo → ir a DEMO_LITE
setState(AppMode::DEMO_LITE);
randomizeOnDemoStart(true);
text_ = "Modo Demo Lite: On";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
showNotificationForAction("MODO DEMO LITE");
}
break;
// Toggle Modo LOGO (easter egg - marca de agua)
case SDLK_K:
toggleLogoMode();
// Mostrar texto informativo
if (current_app_mode_ == AppMode::LOGO) {
text_ = "Modo Logo: On";
// Ya estamos en LOGO → volver a SANDBOX
exitLogoMode(false);
showNotificationForAction("MODO SANDBOX");
} else {
text_ = "Modo Logo: Off";
// Estamos en otro modo → ir a LOGO
enterLogoMode(false);
showNotificationForAction("MODO LOGO");
}
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
break;
}
}
@@ -1039,12 +981,12 @@ void Engine::initBalls(int value) {
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));
}
setText(); // Actualiza el texto
// NOTA: setText() removido - las notificaciones ahora se llaman manualmente desde cada tecla
}
void Engine::setText() {
// Suprimir textos durante modos demo
if (current_app_mode_ != AppMode::MANUAL) return;
if (current_app_mode_ != AppMode::SANDBOX) return;
// Generar texto de número de pelotas
int num_balls = BALL_COUNT_SCENARIOS[scenario_];
@@ -1091,6 +1033,34 @@ void Engine::setText() {
text_init_time_ = SDL_GetTicks();
}
void Engine::showNotificationForAction(const std::string& text) {
// IMPORTANTE: Esta función solo se llama desde handlers de teclado (acciones manuales)
// NUNCA se llama desde código automático (DEMO/LOGO), por lo tanto siempre mostramos notificación
// Obtener color del tema actual para el texto
int text_r, text_g, text_b;
theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b);
SDL_Color notification_color = {
static_cast<Uint8>(text_r),
static_cast<Uint8>(text_g),
static_cast<Uint8>(text_b),
255
};
// Obtener color de fondo de la notificación desde el tema
int bg_r, bg_g, bg_b;
theme_manager_->getCurrentNotificationBackgroundColor(bg_r, bg_g, bg_b);
SDL_Color notification_bg_color = {
static_cast<Uint8>(bg_r),
static_cast<Uint8>(bg_g),
static_cast<Uint8>(bg_b),
255
};
// Mostrar notificación
notifier_.show(text, NOTIFICATION_DURATION, notification_color, notification_bg_color);
}
void Engine::pushBallsAwayFromGravity() {
for (auto& ball : balls_) {
const int SIGNO = ((rand() % 2) * 2) - 1;
@@ -1552,7 +1522,7 @@ void Engine::setState(AppMode new_mode) {
void Engine::updateDemoMode() {
// Verificar si algún modo demo está activo (DEMO, DEMO_LITE o LOGO)
if (current_app_mode_ == AppMode::MANUAL) return;
if (current_app_mode_ == AppMode::SANDBOX) return;
// Actualizar timer
demo_timer_ += delta_time_;
@@ -1692,7 +1662,7 @@ void Engine::updateDemoMode() {
// Solo salir automáticamente si NO llegamos desde MANUAL
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
if (previous_app_mode_ != AppMode::MANUAL && rand() % 100 < 60) {
if (previous_app_mode_ != AppMode::SANDBOX && rand() % 100 < 60) {
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
}
}
@@ -1891,7 +1861,7 @@ void Engine::performDemoAction(bool is_lite) {
// Cambiar sprite (2%)
accumulated_weight += DEMO_WEIGHT_SPRITE;
if (random_value < accumulated_weight) {
switchTexture();
switchTexture(false); // Suprimir notificación en modo automático
return;
}
}
@@ -1934,7 +1904,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
// 3. Sprite
if (rand() % 2 == 0) {
switchTexture();
switchTexture(false); // Suprimir notificación al activar modo DEMO
}
// 4. Física o Figura
@@ -2087,7 +2057,7 @@ void Engine::exitLogoMode(bool return_to_demo) {
if (!return_to_demo) {
// Salida manual (tecla K): volver a MANUAL
setState(AppMode::MANUAL);
setState(AppMode::SANDBOX);
} else {
// Volver al modo previo (DEMO o DEMO_LITE)
setState(previous_app_mode_);
@@ -2147,7 +2117,7 @@ void Engine::updateBallSizes(int old_size, int new_size) {
}
}
void Engine::switchTexture() {
void Engine::switchTexture(bool show_notification) {
if (textures_.empty()) return;
// Guardar tamaño antiguo
@@ -2169,16 +2139,11 @@ void Engine::switchTexture() {
// Ajustar posiciones según el cambio de tamaño
updateBallSizes(old_size, new_size);
// Mostrar texto informativo (solo si NO estamos en modo demo)
if (current_app_mode_ == AppMode::MANUAL) {
// Obtener nombre de textura (uppercase)
// Mostrar notificación con el nombre de la textura (solo si se solicita)
if (show_notification) {
std::string texture_name = texture_names_[current_texture_index_];
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
text_ = "Sprite: " + texture_name;
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
showNotificationForAction("Sprite: " + texture_name);
}
}
@@ -2220,7 +2185,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
}
// Mostrar texto informativo (solo si NO estamos en modo demo o logo)
if (current_app_mode_ == AppMode::MANUAL) {
if (current_app_mode_ == AppMode::SANDBOX) {
text_ = "Modo Física";
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
text_init_time_ = SDL_GetTicks();
@@ -2284,7 +2249,7 @@ void Engine::activateShape(ShapeType type) {
}
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo)
if (active_shape_ && current_app_mode_ == AppMode::MANUAL) {
if (active_shape_ && current_app_mode_ == AppMode::SANDBOX) {
text_ = std::string("Modo ") + active_shape_->getName();
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
text_init_time_ = SDL_GetTicks();