Compare commits
13 Commits
f00b08b6be
...
5f89299444
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f89299444 | |||
| 33728857ac | |||
| d2f170d313 | |||
| aa57ac7012 | |||
| 0d069da29d | |||
| eb3dd03579 | |||
| a1e2c03efd | |||
| 684ac9823b | |||
| 82e5b6798c | |||
| d93ac04ee3 | |||
| 10a4234d49 | |||
| 0d1608712b | |||
| 68381dc92d |
@@ -25,7 +25,7 @@ if (NOT SDL3_ttf_FOUND)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Archivos fuente (excluir main_old.cpp)
|
# Archivos fuente (excluir main_old.cpp)
|
||||||
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp source/themes/*.cpp source/text/*.cpp)
|
file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/shapes/*.cpp source/themes/*.cpp source/text/*.cpp source/ui/*.cpp)
|
||||||
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
|
list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp")
|
||||||
|
|
||||||
# Comprobar si se encontraron archivos fuente
|
# Comprobar si se encontraron archivos fuente
|
||||||
|
|||||||
@@ -22,9 +22,18 @@ constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones
|
|||||||
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)
|
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)
|
||||||
|
|
||||||
// Configuración de interfaz
|
// Configuración de interfaz
|
||||||
constexpr Uint64 TEXT_DURATION = 2000; // Duración del texto informativo (ms)
|
constexpr Uint64 TEXT_DURATION = 2000; // Duración del texto informativo (ms) - OBSOLETO, usar NOTIFICATION_DURATION
|
||||||
constexpr float THEME_TRANSITION_DURATION = 0.5f; // Duración de transiciones LERP entre temas (segundos)
|
constexpr float THEME_TRANSITION_DURATION = 0.5f; // Duración de transiciones LERP entre temas (segundos)
|
||||||
|
|
||||||
|
// Configuración de notificaciones (sistema Notifier)
|
||||||
|
constexpr int TEXT_ABSOLUTE_SIZE = 12; // Tamaño fuente base en píxeles físicos (múltiplo de 12px, tamaño nativo de la fuente)
|
||||||
|
constexpr Uint64 NOTIFICATION_DURATION = 2000; // Duración default de notificaciones (ms)
|
||||||
|
constexpr Uint64 NOTIFICATION_SLIDE_TIME = 300; // Duración animación entrada (ms)
|
||||||
|
constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida (ms)
|
||||||
|
constexpr float NOTIFICATION_BG_ALPHA = 0.7f; // Opacidad fondo semitransparente (0.0-1.0)
|
||||||
|
constexpr int NOTIFICATION_PADDING = 10; // Padding interno del fondo (píxeles físicos)
|
||||||
|
constexpr int NOTIFICATION_TOP_MARGIN = 20; // Margen superior desde borde pantalla (píxeles físicos)
|
||||||
|
|
||||||
// Configuración de pérdida aleatoria en rebotes
|
// Configuración de pérdida aleatoria en rebotes
|
||||||
constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas
|
constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas
|
||||||
constexpr float BOUNCE_RANDOM_LOSS_PERCENT = 0.1f; // 0-10% pérdida adicional aleatoria en cada rebote
|
constexpr float BOUNCE_RANDOM_LOSS_PERCENT = 0.1f; // 0-10% pérdida adicional aleatoria en cada rebote
|
||||||
@@ -65,6 +74,9 @@ struct DynamicThemeKeyframe {
|
|||||||
float bg_top_r, bg_top_g, bg_top_b;
|
float bg_top_r, bg_top_g, bg_top_b;
|
||||||
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
|
||||||
|
|
||||||
|
// Color de fondo de notificaciones
|
||||||
|
int notif_bg_r, notif_bg_g, notif_bg_b;
|
||||||
|
|
||||||
// Colores de pelotas en este keyframe
|
// Colores de pelotas en este keyframe
|
||||||
std::vector<Color> ball_colors;
|
std::vector<Color> ball_colors;
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
window_zoom = 1;
|
window_zoom = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Guardar zoom calculado ANTES de crear la ventana (para F1/F2/F3/F4)
|
||||||
|
current_window_zoom_ = window_zoom;
|
||||||
|
|
||||||
// Calcular tamaño de ventana
|
// Calcular tamaño de ventana
|
||||||
int window_width = logical_width * window_zoom;
|
int window_width = logical_width * window_zoom;
|
||||||
int window_height = logical_height * window_zoom;
|
int window_height = logical_height * window_zoom;
|
||||||
@@ -96,6 +99,8 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
// SDL ya inicializado arriba para validación
|
// SDL ya inicializado arriba para validación
|
||||||
{
|
{
|
||||||
// Crear ventana principal (fullscreen si se especifica)
|
// Crear ventana principal (fullscreen si se especifica)
|
||||||
|
// NOTA: SDL_WINDOW_HIGH_PIXEL_DENSITY removido por incompatibilidad con STRETCH mode (F4)
|
||||||
|
// El DPI se detectará manualmente con SDL_GetWindowSizeInPixels()
|
||||||
Uint32 window_flags = SDL_WINDOW_OPENGL;
|
Uint32 window_flags = SDL_WINDOW_OPENGL;
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
window_flags |= SDL_WINDOW_FULLSCREEN;
|
window_flags |= SDL_WINDOW_FULLSCREEN;
|
||||||
@@ -106,6 +111,11 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
|
std::cout << "¡No se pudo crear la ventana! Error de SDL: " << SDL_GetError() << std::endl;
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
|
// Centrar ventana en pantalla si no está en fullscreen
|
||||||
|
if (!fullscreen) {
|
||||||
|
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
}
|
||||||
|
|
||||||
// Crear renderizador
|
// Crear renderizador
|
||||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||||
if (renderer_ == nullptr) {
|
if (renderer_ == nullptr) {
|
||||||
@@ -203,26 +213,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
|
|
||||||
srand(static_cast<unsigned>(time(nullptr)));
|
srand(static_cast<unsigned>(time(nullptr)));
|
||||||
|
|
||||||
// Calcular tamaño de fuente escalado según resolución
|
// Inicializar ThemeManager PRIMERO (requerido por Notifier)
|
||||||
// Usa las constantes configurables de la clase
|
|
||||||
int font_size = (base_screen_height_ * TEXT_BASE_SIZE) / 240;
|
|
||||||
|
|
||||||
// Inicializar TextRenderer para display (texto centrado)
|
|
||||||
if (!text_renderer_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING)) {
|
|
||||||
SDL_Log("Error al inicializar TextRenderer (display)");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializar TextRenderer para debug (HUD)
|
|
||||||
if (!text_renderer_debug_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING)) {
|
|
||||||
SDL_Log("Error al inicializar TextRenderer (debug)");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicializar ThemeManager
|
|
||||||
theme_manager_ = std::make_unique<ThemeManager>();
|
theme_manager_ = std::make_unique<ThemeManager>();
|
||||||
theme_manager_->initialize();
|
theme_manager_->initialize();
|
||||||
|
|
||||||
|
// Calcular tamaño físico de ventana y tamaño de fuente absoluto
|
||||||
|
// NOTA: Debe llamarse DESPUÉS de inicializar ThemeManager porque notifier_.init() lo necesita
|
||||||
|
updatePhysicalWindowSize();
|
||||||
|
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,11 +296,14 @@ void Engine::update() {
|
|||||||
updateShape();
|
updateShape();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar texto (sin cambios en la lógica)
|
// Actualizar texto (OBSOLETO: sistema antiguo, se mantiene por compatibilidad temporal)
|
||||||
if (show_text_) {
|
if (show_text_) {
|
||||||
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
|
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actualizar sistema de notificaciones
|
||||||
|
notifier_.update(current_time);
|
||||||
|
|
||||||
// Actualizar Modo DEMO (auto-play)
|
// Actualizar Modo DEMO (auto-play)
|
||||||
updateDemoMode();
|
updateDemoMode();
|
||||||
|
|
||||||
@@ -337,8 +338,12 @@ void Engine::handleEvents() {
|
|||||||
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
|
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
|
||||||
|
showNotificationForAction("Gravedad Off");
|
||||||
} else {
|
} else {
|
||||||
switchBallsGravity(); // Toggle normal en modo física
|
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;
|
break;
|
||||||
|
|
||||||
@@ -351,6 +356,7 @@ void Engine::handleEvents() {
|
|||||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
changeGravityDirection(GravityDirection::UP);
|
changeGravityDirection(GravityDirection::UP);
|
||||||
|
showNotificationForAction("Gravedad Arriba");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
@@ -361,6 +367,7 @@ void Engine::handleEvents() {
|
|||||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
changeGravityDirection(GravityDirection::DOWN);
|
changeGravityDirection(GravityDirection::DOWN);
|
||||||
|
showNotificationForAction("Gravedad Abajo");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_LEFT:
|
case SDLK_LEFT:
|
||||||
@@ -371,6 +378,7 @@ void Engine::handleEvents() {
|
|||||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
changeGravityDirection(GravityDirection::LEFT);
|
changeGravityDirection(GravityDirection::LEFT);
|
||||||
|
showNotificationForAction("Gravedad Izquierda");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_RIGHT:
|
case SDLK_RIGHT:
|
||||||
@@ -381,6 +389,7 @@ void Engine::handleEvents() {
|
|||||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
changeGravityDirection(GravityDirection::RIGHT);
|
changeGravityDirection(GravityDirection::RIGHT);
|
||||||
|
showNotificationForAction("Gravedad Derecha");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_V:
|
case SDLK_V:
|
||||||
@@ -394,43 +403,61 @@ void Engine::handleEvents() {
|
|||||||
// Toggle Física ↔ Última Figura (antes era C)
|
// Toggle Física ↔ Última Figura (antes era C)
|
||||||
case SDLK_F:
|
case SDLK_F:
|
||||||
toggleShapeMode();
|
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;
|
break;
|
||||||
|
|
||||||
// Selección directa de figuras 3D
|
// Selección directa de figuras 3D
|
||||||
case SDLK_Q:
|
case SDLK_Q:
|
||||||
activateShape(ShapeType::SPHERE);
|
activateShape(ShapeType::SPHERE);
|
||||||
|
showNotificationForAction("Esfera");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_W:
|
case SDLK_W:
|
||||||
activateShape(ShapeType::LISSAJOUS);
|
activateShape(ShapeType::LISSAJOUS);
|
||||||
|
showNotificationForAction("Lissajous");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_E:
|
case SDLK_E:
|
||||||
activateShape(ShapeType::HELIX);
|
activateShape(ShapeType::HELIX);
|
||||||
|
showNotificationForAction("Hélice");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_R:
|
case SDLK_R:
|
||||||
activateShape(ShapeType::TORUS);
|
activateShape(ShapeType::TORUS);
|
||||||
|
showNotificationForAction("Toroide");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_T:
|
case SDLK_T:
|
||||||
activateShape(ShapeType::CUBE);
|
activateShape(ShapeType::CUBE);
|
||||||
|
showNotificationForAction("Cubo");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_Y:
|
case SDLK_Y:
|
||||||
activateShape(ShapeType::CYLINDER);
|
activateShape(ShapeType::CYLINDER);
|
||||||
|
showNotificationForAction("Cilindro");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_U:
|
case SDLK_U:
|
||||||
activateShape(ShapeType::ICOSAHEDRON);
|
activateShape(ShapeType::ICOSAHEDRON);
|
||||||
|
showNotificationForAction("Icosaedro");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_I:
|
case SDLK_I:
|
||||||
activateShape(ShapeType::ATOM);
|
activateShape(ShapeType::ATOM);
|
||||||
|
showNotificationForAction("Átomo");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_O:
|
case SDLK_O:
|
||||||
activateShape(ShapeType::PNG_SHAPE);
|
activateShape(ShapeType::PNG_SHAPE);
|
||||||
|
showNotificationForAction("Forma PNG");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Ciclar temas de color (movido de T a B)
|
// Ciclar temas de color (movido de T a B)
|
||||||
@@ -446,13 +473,8 @@ void Engine::handleEvents() {
|
|||||||
theme_manager_->cycleTheme();
|
theme_manager_->cycleTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
// Mostrar notificación con el nombre del tema
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -462,12 +484,7 @@ void Engine::handleEvents() {
|
|||||||
{
|
{
|
||||||
int theme_index = (theme_page_ == 0) ? 0 : 10;
|
int theme_index = (theme_page_ == 0) ? 0 : 10;
|
||||||
theme_manager_->switchToTheme(theme_index);
|
theme_manager_->switchToTheme(theme_index);
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -476,12 +493,7 @@ void Engine::handleEvents() {
|
|||||||
{
|
{
|
||||||
int theme_index = (theme_page_ == 0) ? 1 : 11;
|
int theme_index = (theme_page_ == 0) ? 1 : 11;
|
||||||
theme_manager_->switchToTheme(theme_index);
|
theme_manager_->switchToTheme(theme_index);
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -490,12 +502,7 @@ void Engine::handleEvents() {
|
|||||||
{
|
{
|
||||||
int theme_index = (theme_page_ == 0) ? 2 : 12;
|
int theme_index = (theme_page_ == 0) ? 2 : 12;
|
||||||
theme_manager_->switchToTheme(theme_index);
|
theme_manager_->switchToTheme(theme_index);
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -504,12 +511,7 @@ void Engine::handleEvents() {
|
|||||||
{
|
{
|
||||||
int theme_index = (theme_page_ == 0) ? 3 : 13;
|
int theme_index = (theme_page_ == 0) ? 3 : 13;
|
||||||
theme_manager_->switchToTheme(theme_index);
|
theme_manager_->switchToTheme(theme_index);
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -518,12 +520,7 @@ void Engine::handleEvents() {
|
|||||||
{
|
{
|
||||||
int theme_index = (theme_page_ == 0) ? 4 : 14;
|
int theme_index = (theme_page_ == 0) ? 4 : 14;
|
||||||
theme_manager_->switchToTheme(theme_index);
|
theme_manager_->switchToTheme(theme_index);
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -531,12 +528,7 @@ void Engine::handleEvents() {
|
|||||||
// Solo página 0: MONOCHROME (5)
|
// Solo página 0: MONOCHROME (5)
|
||||||
if (theme_page_ == 0) {
|
if (theme_page_ == 0) {
|
||||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
theme_manager_->switchToTheme(5); // MONOCHROME
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -544,12 +536,7 @@ void Engine::handleEvents() {
|
|||||||
// Solo página 0: LAVENDER (6)
|
// Solo página 0: LAVENDER (6)
|
||||||
if (theme_page_ == 0) {
|
if (theme_page_ == 0) {
|
||||||
theme_manager_->switchToTheme(6); // LAVENDER
|
theme_manager_->switchToTheme(6); // LAVENDER
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -557,12 +544,7 @@ void Engine::handleEvents() {
|
|||||||
// Solo página 0: CRIMSON (7)
|
// Solo página 0: CRIMSON (7)
|
||||||
if (theme_page_ == 0) {
|
if (theme_page_ == 0) {
|
||||||
theme_manager_->switchToTheme(7); // CRIMSON
|
theme_manager_->switchToTheme(7); // CRIMSON
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -570,12 +552,7 @@ void Engine::handleEvents() {
|
|||||||
// Solo página 0: EMERALD (8)
|
// Solo página 0: EMERALD (8)
|
||||||
if (theme_page_ == 0) {
|
if (theme_page_ == 0) {
|
||||||
theme_manager_->switchToTheme(8); // EMERALD
|
theme_manager_->switchToTheme(8); // EMERALD
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -583,12 +560,7 @@ void Engine::handleEvents() {
|
|||||||
// Solo página 0: SUNRISE (9)
|
// Solo página 0: SUNRISE (9)
|
||||||
if (theme_page_ == 0) {
|
if (theme_page_ == 0) {
|
||||||
theme_manager_->switchToTheme(9); // SUNRISE
|
theme_manager_->switchToTheme(9); // SUNRISE
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||||
text_ = theme_manager_->getCurrentThemeNameES();
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -596,14 +568,7 @@ void Engine::handleEvents() {
|
|||||||
case SDLK_KP_ENTER:
|
case SDLK_KP_ENTER:
|
||||||
// Alternar entre página 0 y página 1
|
// Alternar entre página 0 y página 1
|
||||||
theme_page_ = (theme_page_ == 0) ? 1 : 0;
|
theme_page_ = (theme_page_ == 0) ? 1 : 0;
|
||||||
|
showNotificationForAction((theme_page_ == 0) ? "Página 1" : "Página 2");
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Cambio de sprite/textura dinámico
|
// Cambio de sprite/textura dinámico
|
||||||
@@ -616,11 +581,7 @@ void Engine::handleEvents() {
|
|||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
shape_scale_factor_ += SHAPE_SCALE_STEP;
|
shape_scale_factor_ += SHAPE_SCALE_STEP;
|
||||||
clampShapeScale();
|
clampShapeScale();
|
||||||
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
|
showNotificationForAction("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;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -628,74 +589,70 @@ void Engine::handleEvents() {
|
|||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
shape_scale_factor_ -= SHAPE_SCALE_STEP;
|
shape_scale_factor_ -= SHAPE_SCALE_STEP;
|
||||||
clampShapeScale();
|
clampShapeScale();
|
||||||
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
|
showNotificationForAction("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;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_KP_MULTIPLY:
|
case SDLK_KP_MULTIPLY:
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
|
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
|
||||||
text_ = "Escala reiniciada (100%)";
|
showNotificationForAction("Escala 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;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_KP_DIVIDE:
|
case SDLK_KP_DIVIDE:
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
depth_zoom_enabled_ = !depth_zoom_enabled_;
|
depth_zoom_enabled_ = !depth_zoom_enabled_;
|
||||||
text_ = depth_zoom_enabled_ ? "Zoom profundidad: On" : "Zoom profundidad: Off";
|
showNotificationForAction(depth_zoom_enabled_ ? "Profundidad On" : "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;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_1:
|
case SDLK_1:
|
||||||
scenario_ = 0;
|
scenario_ = 0;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("10 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_2:
|
case SDLK_2:
|
||||||
scenario_ = 1;
|
scenario_ = 1;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("50 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_3:
|
case SDLK_3:
|
||||||
scenario_ = 2;
|
scenario_ = 2;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("100 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_4:
|
case SDLK_4:
|
||||||
scenario_ = 3;
|
scenario_ = 3;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("500 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_5:
|
case SDLK_5:
|
||||||
scenario_ = 4;
|
scenario_ = 4;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("1,000 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_6:
|
case SDLK_6:
|
||||||
scenario_ = 5;
|
scenario_ = 5;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("5,000 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_7:
|
case SDLK_7:
|
||||||
scenario_ = 6;
|
scenario_ = 6;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("10,000 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_8:
|
case SDLK_8:
|
||||||
scenario_ = 7;
|
scenario_ = 7;
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
|
showNotificationForAction("50,000 Pelotas");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
||||||
@@ -732,22 +689,16 @@ void Engine::handleEvents() {
|
|||||||
if (event.key.mod & SDL_KMOD_SHIFT) {
|
if (event.key.mod & SDL_KMOD_SHIFT) {
|
||||||
theme_manager_->pauseDynamic();
|
theme_manager_->pauseDynamic();
|
||||||
} else {
|
} else {
|
||||||
// D sin Shift = Toggle modo DEMO
|
// D sin Shift = Toggle DEMO ↔ SANDBOX
|
||||||
if (current_app_mode_ == AppMode::DEMO) {
|
if (current_app_mode_ == AppMode::DEMO) {
|
||||||
// Desactivar DEMO → MANUAL
|
// Ya estamos en DEMO → volver a SANDBOX
|
||||||
setState(AppMode::MANUAL);
|
setState(AppMode::SANDBOX);
|
||||||
text_ = "Modo Demo: Off";
|
showNotificationForAction("MODO SANDBOX");
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
} else {
|
} else {
|
||||||
// Activar DEMO (desde cualquier otro modo)
|
// Estamos en otro modo → ir a DEMO
|
||||||
setState(AppMode::DEMO);
|
setState(AppMode::DEMO);
|
||||||
randomizeOnDemoStart(false);
|
randomizeOnDemoStart(false);
|
||||||
text_ = "Modo Demo: On";
|
showNotificationForAction("MODO DEMO");
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -755,35 +706,28 @@ void Engine::handleEvents() {
|
|||||||
// Toggle Modo DEMO LITE (solo física/figuras)
|
// Toggle Modo DEMO LITE (solo física/figuras)
|
||||||
case SDLK_L:
|
case SDLK_L:
|
||||||
if (current_app_mode_ == AppMode::DEMO_LITE) {
|
if (current_app_mode_ == AppMode::DEMO_LITE) {
|
||||||
// Desactivar DEMO_LITE → MANUAL
|
// Ya estamos en DEMO_LITE → volver a SANDBOX
|
||||||
setState(AppMode::MANUAL);
|
setState(AppMode::SANDBOX);
|
||||||
text_ = "Modo Demo Lite: Off";
|
showNotificationForAction("MODO SANDBOX");
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
} else {
|
} else {
|
||||||
// Activar DEMO_LITE (desde cualquier otro modo)
|
// Estamos en otro modo → ir a DEMO_LITE
|
||||||
setState(AppMode::DEMO_LITE);
|
setState(AppMode::DEMO_LITE);
|
||||||
randomizeOnDemoStart(true);
|
randomizeOnDemoStart(true);
|
||||||
text_ = "Modo Demo Lite: On";
|
showNotificationForAction("MODO DEMO LITE");
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Toggle Modo LOGO (easter egg - marca de agua)
|
// Toggle Modo LOGO (easter egg - marca de agua)
|
||||||
case SDLK_K:
|
case SDLK_K:
|
||||||
toggleLogoMode();
|
|
||||||
// Mostrar texto informativo
|
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
text_ = "Modo Logo: On";
|
// Ya estamos en LOGO → volver a SANDBOX
|
||||||
|
exitLogoMode(false);
|
||||||
|
showNotificationForAction("MODO SANDBOX");
|
||||||
} else {
|
} 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -883,6 +827,10 @@ void Engine::render() {
|
|||||||
SDL_RenderGeometry(renderer_, texture_->getSDLTexture(), batch_vertices_.data(), static_cast<int>(batch_vertices_.size()), batch_indices_.data(), static_cast<int>(batch_indices_.size()));
|
SDL_RenderGeometry(renderer_, texture_->getSDLTexture(), batch_vertices_.data(), static_cast<int>(batch_vertices_.size()), batch_indices_.data(), static_cast<int>(batch_indices_.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SISTEMA DE TEXTO ANTIGUO DESHABILITADO
|
||||||
|
// Reemplazado completamente por el sistema de notificaciones (Notifier)
|
||||||
|
// El doble renderizado causaba que aparecieran textos duplicados detrás de las notificaciones
|
||||||
|
/*
|
||||||
if (show_text_) {
|
if (show_text_) {
|
||||||
// Obtener datos del tema actual (delegado a ThemeManager)
|
// Obtener datos del tema actual (delegado a ThemeManager)
|
||||||
int text_color_r, text_color_g, text_color_b;
|
int text_color_r, text_color_g, text_color_b;
|
||||||
@@ -894,7 +842,7 @@ void Engine::render() {
|
|||||||
int margin = 8;
|
int margin = 8;
|
||||||
|
|
||||||
// Texto del número de pelotas con color del tema
|
// Texto del número de pelotas con color del tema
|
||||||
text_renderer_.print(text_pos_, margin, text_.c_str(), text_color_r, text_color_g, text_color_b);
|
text_renderer_.printPhysical(text_pos_, margin, text_.c_str(), text_color_r, text_color_g, text_color_b, text_scale_x, text_scale_y);
|
||||||
|
|
||||||
// Mostrar nombre del tema en castellano debajo del número de pelotas
|
// Mostrar nombre del tema en castellano debajo del número de pelotas
|
||||||
// (solo si text_ NO es ya el nombre del tema, para evitar duplicación)
|
// (solo si text_ NO es ya el nombre del tema, para evitar duplicación)
|
||||||
@@ -904,24 +852,25 @@ void Engine::render() {
|
|||||||
int theme_y = margin + line_height; // Espaciado dinámico
|
int theme_y = margin + line_height; // Espaciado dinámico
|
||||||
|
|
||||||
// Texto del nombre del tema con el mismo color
|
// Texto del nombre del tema con el mismo color
|
||||||
text_renderer_.print(theme_x, theme_y, theme_name_es, text_color_r, text_color_g, text_color_b);
|
text_renderer_.printPhysical(theme_x, theme_y, theme_name_es, text_color_r, text_color_g, text_color_b, text_scale_x, text_scale_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Debug display (solo si está activado con tecla H)
|
// Debug display (solo si está activado con tecla H)
|
||||||
if (show_debug_) {
|
if (show_debug_) {
|
||||||
// Obtener altura de línea para espaciado dinámico (usando fuente debug)
|
// Obtener altura de línea para espaciado dinámico (usando fuente debug)
|
||||||
int line_height = text_renderer_debug_.getTextHeight();
|
int line_height = text_renderer_debug_.getTextHeight();
|
||||||
int margin = 8; // Margen constante
|
int margin = 8; // Margen constante en píxeles físicos
|
||||||
int current_y = margin; // Y inicial
|
int current_y = margin; // Y inicial en píxeles físicos
|
||||||
|
|
||||||
// Mostrar contador de FPS en esquina superior derecha
|
// Mostrar contador de FPS en esquina superior derecha
|
||||||
int fps_text_width = text_renderer_debug_.getTextWidth(fps_text_.c_str());
|
int fps_text_width = text_renderer_debug_.getTextWidthPhysical(fps_text_.c_str());
|
||||||
int fps_x = current_screen_width_ - fps_text_width - margin;
|
int fps_x = physical_window_width_ - fps_text_width - margin;
|
||||||
text_renderer_debug_.print(fps_x, current_y, fps_text_.c_str(), 255, 255, 0); // Amarillo
|
text_renderer_debug_.printAbsolute(fps_x, current_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo
|
||||||
|
|
||||||
// Mostrar estado V-Sync en esquina superior izquierda
|
// Mostrar estado V-Sync en esquina superior izquierda
|
||||||
text_renderer_debug_.print(margin, current_y, vsync_text_.c_str(), 0, 255, 255); // Cian
|
text_renderer_debug_.printAbsolute(margin, current_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Debug: Mostrar valores de la primera pelota (si existe)
|
// Debug: Mostrar valores de la primera pelota (si existe)
|
||||||
@@ -929,35 +878,35 @@ void Engine::render() {
|
|||||||
// Línea 1: Gravedad
|
// Línea 1: Gravedad
|
||||||
int grav_int = static_cast<int>(balls_[0]->getGravityForce());
|
int grav_int = static_cast<int>(balls_[0]->getGravityForce());
|
||||||
std::string grav_text = "Gravedad: " + std::to_string(grav_int);
|
std::string grav_text = "Gravedad: " + std::to_string(grav_int);
|
||||||
text_renderer_debug_.print(margin, current_y, grav_text.c_str(), 255, 0, 255); // Magenta
|
text_renderer_debug_.printAbsolute(margin, current_y, grav_text.c_str(), {255, 0, 255, 255}); // Magenta
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Línea 2: Velocidad Y
|
// Línea 2: Velocidad Y
|
||||||
int vy_int = static_cast<int>(balls_[0]->getVelocityY());
|
int vy_int = static_cast<int>(balls_[0]->getVelocityY());
|
||||||
std::string vy_text = "Velocidad Y: " + std::to_string(vy_int);
|
std::string vy_text = "Velocidad Y: " + std::to_string(vy_int);
|
||||||
text_renderer_debug_.print(margin, current_y, vy_text.c_str(), 255, 0, 255); // Magenta
|
text_renderer_debug_.printAbsolute(margin, current_y, vy_text.c_str(), {255, 0, 255, 255}); // Magenta
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Línea 3: Estado superficie
|
// Línea 3: Estado superficie
|
||||||
std::string surface_text = balls_[0]->isOnSurface() ? "Superficie: Sí" : "Superficie: No";
|
std::string surface_text = balls_[0]->isOnSurface() ? "Superficie: Sí" : "Superficie: No";
|
||||||
text_renderer_debug_.print(margin, current_y, surface_text.c_str(), 255, 0, 255); // Magenta
|
text_renderer_debug_.printAbsolute(margin, current_y, surface_text.c_str(), {255, 0, 255, 255}); // Magenta
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Línea 4: Coeficiente de rebote (loss)
|
// Línea 4: Coeficiente de rebote (loss)
|
||||||
float loss_val = balls_[0]->getLossCoefficient();
|
float loss_val = balls_[0]->getLossCoefficient();
|
||||||
std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4);
|
std::string loss_text = "Rebote: " + std::to_string(loss_val).substr(0, 4);
|
||||||
text_renderer_debug_.print(margin, current_y, loss_text.c_str(), 255, 0, 255); // Magenta
|
text_renderer_debug_.printAbsolute(margin, current_y, loss_text.c_str(), {255, 0, 255, 255}); // Magenta
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Línea 5: Dirección de gravedad
|
// Línea 5: Dirección de gravedad
|
||||||
std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(current_gravity_);
|
std::string gravity_dir_text = "Dirección: " + gravityDirectionToString(current_gravity_);
|
||||||
text_renderer_debug_.print(margin, current_y, gravity_dir_text.c_str(), 255, 255, 0); // Amarillo
|
text_renderer_debug_.printAbsolute(margin, current_y, gravity_dir_text.c_str(), {255, 255, 0, 255}); // Amarillo
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Mostrar tema actual (delegado a ThemeManager)
|
// Debug: Mostrar tema actual (delegado a ThemeManager)
|
||||||
std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN();
|
std::string theme_text = std::string("Tema: ") + theme_manager_->getCurrentThemeNameEN();
|
||||||
text_renderer_debug_.print(margin, current_y, theme_text.c_str(), 255, 255, 128); // Amarillo claro
|
text_renderer_debug_.printAbsolute(margin, current_y, theme_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Debug: Mostrar modo de simulación actual
|
// Debug: Mostrar modo de simulación actual
|
||||||
@@ -969,14 +918,14 @@ void Engine::render() {
|
|||||||
} else {
|
} else {
|
||||||
mode_text = "Modo: Forma";
|
mode_text = "Modo: Forma";
|
||||||
}
|
}
|
||||||
text_renderer_debug_.print(margin, current_y, mode_text.c_str(), 0, 255, 128); // Verde claro
|
text_renderer_debug_.printAbsolute(margin, current_y, mode_text.c_str(), {0, 255, 128, 255}); // Verde claro
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
|
|
||||||
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
|
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
|
||||||
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
|
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
|
||||||
int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f);
|
int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f);
|
||||||
std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%";
|
std::string convergence_text = "Convergencia: " + std::to_string(convergence_percent) + "%";
|
||||||
text_renderer_debug_.print(margin, current_y, convergence_text.c_str(), 255, 128, 0); // Naranja
|
text_renderer_debug_.printAbsolute(margin, current_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja
|
||||||
current_y += line_height;
|
current_y += line_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,22 +934,25 @@ void Engine::render() {
|
|||||||
int fixed_y = margin + (line_height * 2); // Tercera fila fija
|
int fixed_y = margin + (line_height * 2); // Tercera fila fija
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
const char* logo_text = "Modo Logo";
|
const char* logo_text = "Modo Logo";
|
||||||
int logo_text_width = text_renderer_debug_.getTextWidth(logo_text);
|
int logo_text_width = text_renderer_debug_.getTextWidthPhysical(logo_text);
|
||||||
int logo_x = (current_screen_width_ - logo_text_width) / 2;
|
int logo_x = (physical_window_width_ - logo_text_width) / 2;
|
||||||
text_renderer_debug_.print(logo_x, fixed_y, logo_text, 255, 128, 0); // Naranja
|
text_renderer_debug_.printAbsolute(logo_x, fixed_y, logo_text, {255, 128, 0, 255}); // Naranja
|
||||||
} else if (current_app_mode_ == AppMode::DEMO) {
|
} else if (current_app_mode_ == AppMode::DEMO) {
|
||||||
const char* demo_text = "Modo Demo";
|
const char* demo_text = "Modo Demo";
|
||||||
int demo_text_width = text_renderer_debug_.getTextWidth(demo_text);
|
int demo_text_width = text_renderer_debug_.getTextWidthPhysical(demo_text);
|
||||||
int demo_x = (current_screen_width_ - demo_text_width) / 2;
|
int demo_x = (physical_window_width_ - demo_text_width) / 2;
|
||||||
text_renderer_debug_.print(demo_x, fixed_y, demo_text, 255, 165, 0); // Naranja
|
text_renderer_debug_.printAbsolute(demo_x, fixed_y, demo_text, {255, 165, 0, 255}); // Naranja
|
||||||
} else if (current_app_mode_ == AppMode::DEMO_LITE) {
|
} else if (current_app_mode_ == AppMode::DEMO_LITE) {
|
||||||
const char* lite_text = "Modo Demo Lite";
|
const char* lite_text = "Modo Demo Lite";
|
||||||
int lite_text_width = text_renderer_debug_.getTextWidth(lite_text);
|
int lite_text_width = text_renderer_debug_.getTextWidthPhysical(lite_text);
|
||||||
int lite_x = (current_screen_width_ - lite_text_width) / 2;
|
int lite_x = (physical_window_width_ - lite_text_width) / 2;
|
||||||
text_renderer_debug_.print(lite_x, fixed_y, lite_text, 255, 200, 0); // Amarillo-naranja
|
text_renderer_debug_.printAbsolute(lite_x, fixed_y, lite_text, {255, 200, 0, 255}); // Amarillo-naranja
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
||||||
|
notifier_.render();
|
||||||
|
|
||||||
SDL_RenderPresent(renderer_);
|
SDL_RenderPresent(renderer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,24 +987,47 @@ void Engine::initBalls(int value) {
|
|||||||
float mass_factor = GRAVITY_MASS_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN);
|
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));
|
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() {
|
void Engine::setText() {
|
||||||
// Suprimir textos durante modos demo
|
// 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_];
|
int num_balls = BALL_COUNT_SCENARIOS[scenario_];
|
||||||
|
std::string notification_text;
|
||||||
if (num_balls == 1) {
|
if (num_balls == 1) {
|
||||||
text_ = "1 pelota";
|
notification_text = "1 Pelota";
|
||||||
|
} else if (num_balls < 1000) {
|
||||||
|
notification_text = std::to_string(num_balls) + " Pelotas";
|
||||||
} else {
|
} else {
|
||||||
text_ = std::to_string(num_balls) + " pelotas";
|
// Formato con separador de miles para números grandes
|
||||||
|
notification_text = std::to_string(num_balls / 1000) + "," +
|
||||||
|
(num_balls % 1000 < 100 ? "0" : "") +
|
||||||
|
(num_balls % 1000 < 10 ? "0" : "") +
|
||||||
|
std::to_string(num_balls % 1000) + " Pelotas";
|
||||||
}
|
}
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2; // Centrar texto
|
|
||||||
|
// Mostrar notificación (colores se obtienen dinámicamente desde ThemeManager)
|
||||||
|
notifier_.show(notification_text, NOTIFICATION_DURATION);
|
||||||
|
|
||||||
|
// Sistema antiguo (mantener temporalmente para compatibilidad)
|
||||||
|
text_ = notification_text;
|
||||||
|
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||||
show_text_ = true;
|
show_text_ = true;
|
||||||
text_init_time_ = SDL_GetTicks();
|
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
|
||||||
|
|
||||||
|
// Los colores se obtienen dinámicamente cada frame desde ThemeManager en render()
|
||||||
|
// Esto permite transiciones LERP suaves y siempre usar el color del tema actual
|
||||||
|
notifier_.show(text, NOTIFICATION_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::pushBallsAwayFromGravity() {
|
void Engine::pushBallsAwayFromGravity() {
|
||||||
for (auto& ball : balls_) {
|
for (auto& ball : balls_) {
|
||||||
const int SIGNO = ((rand() % 2) * 2) - 1;
|
const int SIGNO = ((rand() % 2) * 2) - 1;
|
||||||
@@ -1145,6 +1120,9 @@ void Engine::toggleFullscreen() {
|
|||||||
|
|
||||||
fullscreen_enabled_ = !fullscreen_enabled_;
|
fullscreen_enabled_ = !fullscreen_enabled_;
|
||||||
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
||||||
|
|
||||||
|
// Actualizar dimensiones físicas después del cambio
|
||||||
|
updatePhysicalWindowSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::toggleRealFullscreen() {
|
void Engine::toggleRealFullscreen() {
|
||||||
@@ -1174,6 +1152,9 @@ void Engine::toggleRealFullscreen() {
|
|||||||
// Actualizar presentación lógica del renderizador
|
// Actualizar presentación lógica del renderizador
|
||||||
SDL_SetRenderLogicalPresentation(renderer_, current_screen_width_, current_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
SDL_SetRenderLogicalPresentation(renderer_, current_screen_width_, current_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||||
|
|
||||||
|
// Actualizar tamaño físico de ventana y fuentes
|
||||||
|
updatePhysicalWindowSize();
|
||||||
|
|
||||||
// Reinicar la escena con nueva resolución
|
// Reinicar la escena con nueva resolución
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
}
|
}
|
||||||
@@ -1184,13 +1165,17 @@ void Engine::toggleRealFullscreen() {
|
|||||||
current_screen_width_ = base_screen_width_;
|
current_screen_width_ = base_screen_width_;
|
||||||
current_screen_height_ = base_screen_height_;
|
current_screen_height_ = base_screen_height_;
|
||||||
|
|
||||||
// Restaurar ventana normal
|
// Restaurar ventana normal con el zoom actual (no hardcoded)
|
||||||
SDL_SetWindowFullscreen(window_, false);
|
SDL_SetWindowFullscreen(window_, false);
|
||||||
SDL_SetWindowSize(window_, base_screen_width_ * DEFAULT_WINDOW_ZOOM, base_screen_height_ * DEFAULT_WINDOW_ZOOM);
|
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
|
||||||
|
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
|
||||||
// Restaurar presentación lógica base
|
// Restaurar presentación lógica base
|
||||||
SDL_SetRenderLogicalPresentation(renderer_, base_screen_width_, base_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
SDL_SetRenderLogicalPresentation(renderer_, base_screen_width_, base_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||||
|
|
||||||
|
// Actualizar tamaño físico de ventana y fuentes
|
||||||
|
updatePhysicalWindowSize();
|
||||||
|
|
||||||
// Reinicar la escena con resolución original
|
// Reinicar la escena con resolución original
|
||||||
initBalls(scenario_);
|
initBalls(scenario_);
|
||||||
}
|
}
|
||||||
@@ -1376,6 +1361,9 @@ void Engine::setWindowZoom(int new_zoom) {
|
|||||||
SDL_SetWindowSize(window_, new_width, new_height);
|
SDL_SetWindowSize(window_, new_width, new_height);
|
||||||
SDL_SetWindowPosition(window_, new_x, new_y);
|
SDL_SetWindowPosition(window_, new_x, new_y);
|
||||||
current_window_zoom_ = new_zoom;
|
current_window_zoom_ = new_zoom;
|
||||||
|
|
||||||
|
// Actualizar tamaño físico de ventana y fuentes
|
||||||
|
updatePhysicalWindowSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::zoomIn() {
|
void Engine::zoomIn() {
|
||||||
@@ -1386,6 +1374,72 @@ void Engine::zoomOut() {
|
|||||||
setWindowZoom(current_window_zoom_ - 1);
|
setWindowZoom(current_window_zoom_ - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::updatePhysicalWindowSize() {
|
||||||
|
if (real_fullscreen_enabled_) {
|
||||||
|
// En fullscreen real (F4), usar resolución del display
|
||||||
|
physical_window_width_ = current_screen_width_;
|
||||||
|
physical_window_height_ = current_screen_height_;
|
||||||
|
} else if (fullscreen_enabled_) {
|
||||||
|
// En fullscreen F3, obtener tamaño REAL del display (no del framebuffer lógico)
|
||||||
|
// SDL_GetRenderOutputSize() falla en F3 (devuelve tamaño lógico 960x720)
|
||||||
|
// Necesitamos el tamaño FÍSICO real de la pantalla
|
||||||
|
int num_displays = 0;
|
||||||
|
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||||
|
if (displays != nullptr && num_displays > 0) {
|
||||||
|
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||||
|
if (dm != nullptr) {
|
||||||
|
physical_window_width_ = dm->w;
|
||||||
|
physical_window_height_ = dm->h;
|
||||||
|
}
|
||||||
|
SDL_free(displays);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// En modo ventana, obtener tamaño FÍSICO real del framebuffer
|
||||||
|
int window_w = 0, window_h = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
||||||
|
physical_window_width_ = window_w;
|
||||||
|
physical_window_height_ = window_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcular tamaño de fuente basado en altura física
|
||||||
|
// Referencia: 8px a 1440p (monitor del usuario)
|
||||||
|
int font_size = (physical_window_height_ * TEXT_BASE_SIZE) / 1440;
|
||||||
|
if (font_size < 6) font_size = 6; // Tamaño mínimo legible
|
||||||
|
|
||||||
|
// Reinicializar TextRenderers con nuevo tamaño de fuente
|
||||||
|
text_renderer_.cleanup();
|
||||||
|
text_renderer_debug_.cleanup();
|
||||||
|
text_renderer_notifier_.cleanup();
|
||||||
|
|
||||||
|
text_renderer_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING);
|
||||||
|
text_renderer_debug_.init(renderer_, TEXT_FONT_PATH, font_size, TEXT_ANTIALIASING);
|
||||||
|
|
||||||
|
// TextRenderer para notificaciones: Detectar DPI y ajustar tamaño
|
||||||
|
// En pantallas Retina/HiDPI, el texto necesita ser más grande para ser legible
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_GetWindowSize(window_, &logical_w, &logical_h);
|
||||||
|
|
||||||
|
// Usar physical_window_width_ que ya contiene el tamaño real del framebuffer
|
||||||
|
// (calculado arriba con SDL_GetRenderOutputSize o current_screen_width_)
|
||||||
|
int pixels_w = physical_window_width_;
|
||||||
|
|
||||||
|
// Calcular escala DPI (1.0 normal, 2.0 Retina, 3.0 en algunos displays)
|
||||||
|
float dpi_scale = (logical_w > 0) ? static_cast<float>(pixels_w) / static_cast<float>(logical_w) : 1.0f;
|
||||||
|
|
||||||
|
// Ajustar tamaño de fuente base (16px) por escala DPI
|
||||||
|
// Retina macOS: 16px * 2.0 = 32px (legible)
|
||||||
|
// Normal: 16px * 1.0 = 16px
|
||||||
|
int notification_font_size = static_cast<int>(TEXT_ABSOLUTE_SIZE * dpi_scale);
|
||||||
|
if (notification_font_size < 12) notification_font_size = 12; // Mínimo legible
|
||||||
|
|
||||||
|
text_renderer_notifier_.init(renderer_, TEXT_FONT_PATH, notification_font_size, TEXT_ANTIALIASING);
|
||||||
|
|
||||||
|
// Inicializar/actualizar Notifier con nuevas dimensiones y ThemeManager
|
||||||
|
// NOTA: init() es seguro de llamar múltiples veces, solo actualiza punteros y dimensiones
|
||||||
|
// Esto asegura que el notifier tenga las referencias correctas tras resize/fullscreen
|
||||||
|
notifier_.init(renderer_, &text_renderer_notifier_, theme_manager_.get(), physical_window_width_, physical_window_height_);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO)
|
// Sistema de gestión de estados (MANUAL/DEMO/DEMO_LITE/LOGO)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1436,7 +1490,7 @@ void Engine::setState(AppMode new_mode) {
|
|||||||
|
|
||||||
void Engine::updateDemoMode() {
|
void Engine::updateDemoMode() {
|
||||||
// Verificar si algún modo demo está activo (DEMO, DEMO_LITE o LOGO)
|
// 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
|
// Actualizar timer
|
||||||
demo_timer_ += delta_time_;
|
demo_timer_ += delta_time_;
|
||||||
@@ -1574,9 +1628,10 @@ void Engine::updateDemoMode() {
|
|||||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solo salir automáticamente si NO llegamos desde MANUAL
|
// Solo salir automáticamente si la entrada a LOGO fue automática (desde DEMO)
|
||||||
|
// No salir si el usuario entró manualmente con tecla K
|
||||||
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
|
// 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 (!logo_entered_manually_ && rand() % 100 < 60) {
|
||||||
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
|
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1775,7 +1830,7 @@ void Engine::performDemoAction(bool is_lite) {
|
|||||||
// Cambiar sprite (2%)
|
// Cambiar sprite (2%)
|
||||||
accumulated_weight += DEMO_WEIGHT_SPRITE;
|
accumulated_weight += DEMO_WEIGHT_SPRITE;
|
||||||
if (random_value < accumulated_weight) {
|
if (random_value < accumulated_weight) {
|
||||||
switchTexture();
|
switchTexture(false); // Suprimir notificación en modo automático
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1783,6 +1838,15 @@ void Engine::performDemoAction(bool is_lite) {
|
|||||||
|
|
||||||
// Randomizar todo al iniciar modo DEMO
|
// Randomizar todo al iniciar modo DEMO
|
||||||
void Engine::randomizeOnDemoStart(bool is_lite) {
|
void Engine::randomizeOnDemoStart(bool is_lite) {
|
||||||
|
// Si venimos de LOGO con PNG_SHAPE, cambiar figura obligatoriamente
|
||||||
|
// PNG_SHAPE es exclusivo del modo LOGO y no debe aparecer en DEMO/DEMO_LITE
|
||||||
|
if (current_shape_type_ == ShapeType::PNG_SHAPE) {
|
||||||
|
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
|
||||||
|
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
|
||||||
|
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||||
|
activateShape(shapes[rand() % 8]);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_lite) {
|
if (is_lite) {
|
||||||
// DEMO LITE: Solo randomizar física/figura + gravedad
|
// DEMO LITE: Solo randomizar física/figura + gravedad
|
||||||
// Elegir aleatoriamente entre modo física o figura
|
// Elegir aleatoriamente entre modo física o figura
|
||||||
@@ -1818,7 +1882,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
|
|||||||
|
|
||||||
// 3. Sprite
|
// 3. Sprite
|
||||||
if (rand() % 2 == 0) {
|
if (rand() % 2 == 0) {
|
||||||
switchTexture();
|
switchTexture(false); // Suprimir notificación al activar modo DEMO
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Física o Figura
|
// 4. Física o Figura
|
||||||
@@ -1884,18 +1948,18 @@ void Engine::enterLogoMode(bool from_demo) {
|
|||||||
logo_previous_texture_index_ = current_texture_index_;
|
logo_previous_texture_index_ = current_texture_index_;
|
||||||
logo_previous_shape_scale_ = shape_scale_factor_;
|
logo_previous_shape_scale_ = shape_scale_factor_;
|
||||||
|
|
||||||
// Buscar índice de textura "tiny"
|
// Buscar índice de textura "small"
|
||||||
size_t tiny_index = current_texture_index_; // Por defecto mantener actual
|
size_t small_index = current_texture_index_; // Por defecto mantener actual
|
||||||
for (size_t i = 0; i < texture_names_.size(); i++) {
|
for (size_t i = 0; i < texture_names_.size(); i++) {
|
||||||
if (texture_names_[i] == "tiny") {
|
if (texture_names_[i] == "small") {
|
||||||
tiny_index = i;
|
small_index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplicar configuración fija del Modo Logo
|
// Aplicar configuración fija del Modo Logo
|
||||||
if (tiny_index != current_texture_index_) {
|
if (small_index != current_texture_index_) {
|
||||||
current_texture_index_ = tiny_index;
|
current_texture_index_ = small_index;
|
||||||
int old_size = current_ball_size_;
|
int old_size = current_ball_size_;
|
||||||
current_ball_size_ = textures_[current_texture_index_]->getWidth();
|
current_ball_size_ = textures_[current_texture_index_]->getWidth();
|
||||||
updateBallSizes(old_size, current_ball_size_);
|
updateBallSizes(old_size, current_ball_size_);
|
||||||
@@ -1907,8 +1971,10 @@ void Engine::enterLogoMode(bool from_demo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cambiar a tema MONOCHROME
|
// Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
|
||||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
int logo_themes[] = {5, 6, 7, 8}; // MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
|
||||||
|
int random_theme = logo_themes[rand() % 4];
|
||||||
|
theme_manager_->switchToTheme(random_theme);
|
||||||
|
|
||||||
// Establecer escala a 120%
|
// Establecer escala a 120%
|
||||||
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
|
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
|
||||||
@@ -1932,6 +1998,9 @@ void Engine::enterLogoMode(bool from_demo) {
|
|||||||
logo_target_flip_percentage_ = 0.0f;
|
logo_target_flip_percentage_ = 0.0f;
|
||||||
logo_current_flip_count_ = 0;
|
logo_current_flip_count_ = 0;
|
||||||
|
|
||||||
|
// Guardar si entrada fue manual (tecla K) o automática (desde DEMO)
|
||||||
|
logo_entered_manually_ = !from_demo;
|
||||||
|
|
||||||
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
|
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
|
||||||
setState(AppMode::LOGO);
|
setState(AppMode::LOGO);
|
||||||
}
|
}
|
||||||
@@ -1969,12 +2038,23 @@ void Engine::exitLogoMode(bool return_to_demo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resetear flag de entrada manual
|
||||||
|
logo_entered_manually_ = false;
|
||||||
|
|
||||||
if (!return_to_demo) {
|
if (!return_to_demo) {
|
||||||
// Salida manual (tecla K): volver a MANUAL
|
// Salida manual (tecla K): volver a MANUAL
|
||||||
setState(AppMode::MANUAL);
|
setState(AppMode::SANDBOX);
|
||||||
} else {
|
} else {
|
||||||
// Volver al modo previo (DEMO o DEMO_LITE)
|
// Volver al modo previo (DEMO o DEMO_LITE)
|
||||||
setState(previous_app_mode_);
|
setState(previous_app_mode_);
|
||||||
|
|
||||||
|
// Si la figura activa es PNG_SHAPE, cambiar a otra figura aleatoria
|
||||||
|
if (current_shape_type_ == ShapeType::PNG_SHAPE) {
|
||||||
|
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX,
|
||||||
|
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
|
||||||
|
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
|
||||||
|
activateShape(shapes[rand() % 8]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2031,7 +2111,7 @@ void Engine::updateBallSizes(int old_size, int new_size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::switchTexture() {
|
void Engine::switchTexture(bool show_notification) {
|
||||||
if (textures_.empty()) return;
|
if (textures_.empty()) return;
|
||||||
|
|
||||||
// Guardar tamaño antiguo
|
// Guardar tamaño antiguo
|
||||||
@@ -2053,16 +2133,11 @@ void Engine::switchTexture() {
|
|||||||
// Ajustar posiciones según el cambio de tamaño
|
// Ajustar posiciones según el cambio de tamaño
|
||||||
updateBallSizes(old_size, new_size);
|
updateBallSizes(old_size, new_size);
|
||||||
|
|
||||||
// Mostrar texto informativo (solo si NO estamos en modo demo)
|
// Mostrar notificación con el nombre de la textura (solo si se solicita)
|
||||||
if (current_app_mode_ == AppMode::MANUAL) {
|
if (show_notification) {
|
||||||
// Obtener nombre de textura (uppercase)
|
|
||||||
std::string texture_name = texture_names_[current_texture_index_];
|
std::string texture_name = texture_names_[current_texture_index_];
|
||||||
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
|
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
|
||||||
|
showNotificationForAction("Sprite: " + texture_name);
|
||||||
text_ = "Sprite: " + texture_name;
|
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
|
||||||
show_text_ = true;
|
|
||||||
text_init_time_ = SDL_GetTicks();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2104,7 +2179,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mostrar texto informativo (solo si NO estamos en modo demo o logo)
|
// 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_ = "Modo Física";
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||||
text_init_time_ = SDL_GetTicks();
|
text_init_time_ = SDL_GetTicks();
|
||||||
@@ -2168,7 +2243,7 @@ void Engine::activateShape(ShapeType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo)
|
// 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_ = std::string("Modo ") + active_shape_->getName();
|
||||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||||
text_init_time_ = SDL_GetTicks();
|
text_init_time_ = SDL_GetTicks();
|
||||||
|
|||||||
@@ -16,10 +16,11 @@
|
|||||||
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
|
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
|
||||||
#include "text/textrenderer.h" // for TextRenderer
|
#include "text/textrenderer.h" // for TextRenderer
|
||||||
#include "theme_manager.h" // for ThemeManager
|
#include "theme_manager.h" // for ThemeManager
|
||||||
|
#include "ui/notifier.h" // for Notifier
|
||||||
|
|
||||||
// Modos de aplicación mutuamente excluyentes
|
// Modos de aplicación mutuamente excluyentes
|
||||||
enum class AppMode {
|
enum class AppMode {
|
||||||
MANUAL, // Control manual del usuario
|
SANDBOX, // Control manual del usuario (modo sandbox)
|
||||||
DEMO, // Modo demo completo (auto-play)
|
DEMO, // Modo demo completo (auto-play)
|
||||||
DEMO_LITE, // Modo demo lite (solo física/figuras)
|
DEMO_LITE, // Modo demo lite (solo física/figuras)
|
||||||
LOGO // Modo logo (easter egg)
|
LOGO // Modo logo (easter egg)
|
||||||
@@ -54,9 +55,11 @@ class Engine {
|
|||||||
|
|
||||||
// UI y debug
|
// UI y debug
|
||||||
bool show_debug_ = false;
|
bool show_debug_ = false;
|
||||||
bool show_text_ = true;
|
bool show_text_ = true; // OBSOLETO: usar notifier_ en su lugar
|
||||||
TextRenderer text_renderer_; // Sistema de renderizado de texto para display (centrado)
|
TextRenderer text_renderer_; // Sistema de renderizado de texto para display (centrado)
|
||||||
TextRenderer text_renderer_debug_; // Sistema de renderizado de texto para debug (HUD)
|
TextRenderer text_renderer_debug_; // Sistema de renderizado de texto para debug (HUD)
|
||||||
|
TextRenderer text_renderer_notifier_; // Sistema de renderizado de texto para notificaciones (tamaño fijo)
|
||||||
|
Notifier notifier_; // Sistema de notificaciones estilo iOS/Android
|
||||||
|
|
||||||
// Sistema de zoom dinámico
|
// Sistema de zoom dinámico
|
||||||
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
|
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
|
||||||
@@ -83,6 +86,10 @@ class Engine {
|
|||||||
int current_screen_width_ = DEFAULT_SCREEN_WIDTH;
|
int current_screen_width_ = DEFAULT_SCREEN_WIDTH;
|
||||||
int current_screen_height_ = DEFAULT_SCREEN_HEIGHT;
|
int current_screen_height_ = DEFAULT_SCREEN_HEIGHT;
|
||||||
|
|
||||||
|
// Resolución física real de ventana/pantalla (para texto absoluto)
|
||||||
|
int physical_window_width_ = DEFAULT_SCREEN_WIDTH;
|
||||||
|
int physical_window_height_ = DEFAULT_SCREEN_HEIGHT;
|
||||||
|
|
||||||
// Sistema de temas (delegado a ThemeManager)
|
// Sistema de temas (delegado a ThemeManager)
|
||||||
std::unique_ptr<ThemeManager> theme_manager_;
|
std::unique_ptr<ThemeManager> theme_manager_;
|
||||||
int theme_page_ = 0; // Página actual de temas (0 o 1) para acceso por Numpad
|
int theme_page_ = 0; // Página actual de temas (0 o 1) para acceso por Numpad
|
||||||
@@ -96,8 +103,8 @@ class Engine {
|
|||||||
bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado
|
bool depth_zoom_enabled_ = true; // Zoom por profundidad Z activado
|
||||||
|
|
||||||
// Sistema de Modo DEMO (auto-play)
|
// Sistema de Modo DEMO (auto-play)
|
||||||
AppMode current_app_mode_ = AppMode::MANUAL; // Modo actual (mutuamente excluyente)
|
AppMode current_app_mode_ = AppMode::SANDBOX; // Modo actual (mutuamente excluyente)
|
||||||
AppMode previous_app_mode_ = AppMode::MANUAL; // Modo previo antes de entrar a LOGO
|
AppMode previous_app_mode_ = AppMode::SANDBOX; // Modo previo antes de entrar a LOGO
|
||||||
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
||||||
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
||||||
|
|
||||||
@@ -113,6 +120,9 @@ class Engine {
|
|||||||
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
|
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
|
||||||
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
|
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
|
||||||
|
|
||||||
|
// Control de entrada manual vs automática a LOGO MODE
|
||||||
|
bool logo_entered_manually_ = false; // true si se activó con tecla K, false si automático desde DEMO
|
||||||
|
|
||||||
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
||||||
int logo_previous_theme_ = 0; // Índice de tema (0-9)
|
int logo_previous_theme_ = 0; // Índice de tema (0-9)
|
||||||
size_t logo_previous_texture_index_ = 0;
|
size_t logo_previous_texture_index_ = 0;
|
||||||
@@ -124,7 +134,7 @@ class Engine {
|
|||||||
|
|
||||||
// Configuración del sistema de texto (constantes configurables)
|
// Configuración del sistema de texto (constantes configurables)
|
||||||
static constexpr const char* TEXT_FONT_PATH = "data/fonts/determination.ttf";
|
static constexpr const char* TEXT_FONT_PATH = "data/fonts/determination.ttf";
|
||||||
static constexpr int TEXT_BASE_SIZE = 8; // Tamaño base para 240p
|
static constexpr int TEXT_BASE_SIZE = 24; // Tamaño base para 240p
|
||||||
static constexpr bool TEXT_ANTIALIASING = true; // true = suavizado, false = píxeles nítidos
|
static constexpr bool TEXT_ANTIALIASING = true; // true = suavizado, false = píxeles nítidos
|
||||||
|
|
||||||
// Métodos principales del loop
|
// Métodos principales del loop
|
||||||
@@ -135,7 +145,8 @@ class Engine {
|
|||||||
|
|
||||||
// Métodos auxiliares
|
// Métodos auxiliares
|
||||||
void initBalls(int value);
|
void initBalls(int value);
|
||||||
void setText();
|
void setText(); // DEPRECATED - usar showNotificationForAction() en su lugar
|
||||||
|
void showNotificationForAction(const std::string& text); // Mostrar notificación solo en modo MANUAL
|
||||||
void pushBallsAwayFromGravity();
|
void pushBallsAwayFromGravity();
|
||||||
void switchBallsGravity();
|
void switchBallsGravity();
|
||||||
void enableBallsGravityIfDisabled();
|
void enableBallsGravityIfDisabled();
|
||||||
@@ -163,14 +174,15 @@ class Engine {
|
|||||||
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
|
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
|
||||||
|
|
||||||
// Sistema de cambio de sprites dinámico
|
// Sistema de cambio de sprites dinámico
|
||||||
void switchTexture(); // Cambia a siguiente textura disponible
|
void switchTexture(bool show_notification = true); // Cambia a siguiente textura disponible
|
||||||
void updateBallSizes(int old_size, int new_size); // Ajusta posiciones al cambiar tamaño
|
void updateBallSizes(int old_size, int new_size); // Ajusta posiciones al cambiar tamaño
|
||||||
|
|
||||||
// Sistema de zoom dinámico
|
// Sistema de zoom dinámico
|
||||||
int calculateMaxWindowZoom() const;
|
int calculateMaxWindowZoom() const;
|
||||||
void setWindowZoom(int new_zoom);
|
void setWindowZoom(int new_zoom);
|
||||||
void zoomIn();
|
void zoomIn();
|
||||||
void zoomOut();
|
void zoomOut();
|
||||||
|
void updatePhysicalWindowSize(); // Actualizar tamaño físico real de ventana
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale = 1.0f);
|
void addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale = 1.0f);
|
||||||
|
|||||||
@@ -90,6 +90,136 @@ void TextRenderer::print(int x, int y, const std::string& text, uint8_t r, uint8
|
|||||||
print(x, y, text.c_str(), r, g, b);
|
print(x, y, text.c_str(), r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextRenderer::printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y) {
|
||||||
|
if (!isInitialized() || text == nullptr || text[0] == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir coordenadas lógicas a físicas
|
||||||
|
int physical_x = static_cast<int>(logical_x * scale_x);
|
||||||
|
int physical_y = static_cast<int>(logical_y * scale_y);
|
||||||
|
|
||||||
|
// Crear superficie con el texto renderizado
|
||||||
|
SDL_Color color = {r, g, b, 255};
|
||||||
|
SDL_Surface* text_surface = nullptr;
|
||||||
|
|
||||||
|
if (use_antialiasing_) {
|
||||||
|
text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color);
|
||||||
|
} else {
|
||||||
|
text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text_surface == nullptr) {
|
||||||
|
SDL_Log("Error al renderizar texto: %s", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear textura desde la superficie
|
||||||
|
SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface);
|
||||||
|
|
||||||
|
if (text_texture == nullptr) {
|
||||||
|
SDL_Log("Error al crear textura: %s", SDL_GetError());
|
||||||
|
SDL_DestroySurface(text_surface);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderizar en coordenadas físicas (bypass presentación lógica)
|
||||||
|
// Usar SDL_RenderTexture con coordenadas absolutas de ventana
|
||||||
|
SDL_FRect dest_rect;
|
||||||
|
dest_rect.x = static_cast<float>(physical_x);
|
||||||
|
dest_rect.y = static_cast<float>(physical_y);
|
||||||
|
dest_rect.w = static_cast<float>(text_surface->w);
|
||||||
|
dest_rect.h = static_cast<float>(text_surface->h);
|
||||||
|
|
||||||
|
// Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_RendererLogicalPresentation presentation_mode;
|
||||||
|
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
|
// Renderizar sin presentación lógica (coordenadas absolutas)
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect);
|
||||||
|
|
||||||
|
// Restaurar presentación lógica
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, logical_w, logical_h, presentation_mode);
|
||||||
|
|
||||||
|
// Limpiar recursos
|
||||||
|
SDL_DestroyTexture(text_texture);
|
||||||
|
SDL_DestroySurface(text_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y) {
|
||||||
|
printPhysical(logical_x, logical_y, text.c_str(), r, g, b, scale_x, scale_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color) {
|
||||||
|
if (!isInitialized() || text == nullptr || text[0] == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear superficie con el texto renderizado
|
||||||
|
SDL_Surface* text_surface = nullptr;
|
||||||
|
|
||||||
|
if (use_antialiasing_) {
|
||||||
|
text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color);
|
||||||
|
} else {
|
||||||
|
text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text_surface == nullptr) {
|
||||||
|
SDL_Log("Error al renderizar texto: %s", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear textura desde la superficie
|
||||||
|
SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface);
|
||||||
|
|
||||||
|
if (text_texture == nullptr) {
|
||||||
|
SDL_Log("Error al crear textura: %s", SDL_GetError());
|
||||||
|
SDL_DestroySurface(text_surface);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar alpha blending si el color tiene transparencia
|
||||||
|
if (color.a < 255) {
|
||||||
|
SDL_SetTextureBlendMode(text_texture, SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_SetTextureAlphaModFloat(text_texture, color.a / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener viewport ANTES de deshabilitar presentación lógica
|
||||||
|
// En modo letterbox (F3), SDL crea un viewport con offset para centrar la imagen
|
||||||
|
SDL_Rect viewport;
|
||||||
|
SDL_GetRenderViewport(renderer_, &viewport);
|
||||||
|
|
||||||
|
// Preparar rectángulo de destino en coordenadas físicas absolutas
|
||||||
|
// Aplicar offset del viewport para que el texto se pinte dentro del área visible
|
||||||
|
SDL_FRect dest_rect;
|
||||||
|
dest_rect.x = static_cast<float>(physical_x + viewport.x);
|
||||||
|
dest_rect.y = static_cast<float>(physical_y + viewport.y);
|
||||||
|
dest_rect.w = static_cast<float>(text_surface->w);
|
||||||
|
dest_rect.h = static_cast<float>(text_surface->h);
|
||||||
|
|
||||||
|
// Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_RendererLogicalPresentation presentation_mode;
|
||||||
|
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
|
// Renderizar sin presentación lógica (coordenadas absolutas con offset de viewport)
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect);
|
||||||
|
|
||||||
|
// Restaurar presentación lógica
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, logical_w, logical_h, presentation_mode);
|
||||||
|
|
||||||
|
// Limpiar recursos
|
||||||
|
SDL_DestroyTexture(text_texture);
|
||||||
|
SDL_DestroySurface(text_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color) {
|
||||||
|
printAbsolute(physical_x, physical_y, text.c_str(), color);
|
||||||
|
}
|
||||||
|
|
||||||
int TextRenderer::getTextWidth(const char* text) {
|
int TextRenderer::getTextWidth(const char* text) {
|
||||||
if (!isInitialized() || text == nullptr) {
|
if (!isInitialized() || text == nullptr) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -103,6 +233,23 @@ int TextRenderer::getTextWidth(const char* text) {
|
|||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int TextRenderer::getTextWidthPhysical(const char* text) {
|
||||||
|
// Retorna el ancho REAL en píxeles físicos (sin escalado lógico)
|
||||||
|
// Idéntico a getTextWidth() pero semánticamente diferente:
|
||||||
|
// - Este método se usa cuando se necesita el ancho REAL de la fuente
|
||||||
|
// - Útil para calcular dimensiones de UI en coordenadas físicas absolutas
|
||||||
|
if (!isInitialized() || text == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
if (!TTF_GetStringSize(font_, text, strlen(text), &width, &height)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return width; // Ancho real de la textura generada por TTF
|
||||||
|
}
|
||||||
|
|
||||||
int TextRenderer::getTextHeight() {
|
int TextRenderer::getTextHeight() {
|
||||||
if (!isInitialized()) {
|
if (!isInitialized()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -19,9 +19,22 @@ public:
|
|||||||
void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b);
|
void print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b);
|
||||||
void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b);
|
void print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
|
||||||
// Obtiene el ancho de un texto renderizado
|
// Renderiza texto en coordenadas lógicas, pero convierte a físicas para tamaño absoluto
|
||||||
|
void printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||||
|
void printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y);
|
||||||
|
|
||||||
|
// Renderiza texto en coordenadas físicas absolutas (tamaño fijo independiente de resolución)
|
||||||
|
// NOTA: Este método usa el tamaño de fuente tal cual fue cargado, sin escalado
|
||||||
|
void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color);
|
||||||
|
void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color);
|
||||||
|
|
||||||
|
// Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad)
|
||||||
int getTextWidth(const char* text);
|
int getTextWidth(const char* text);
|
||||||
|
|
||||||
|
// Obtiene el ancho de un texto en píxeles FÍSICOS reales (sin escalado)
|
||||||
|
// Útil para notificaciones y elementos UI de tamaño fijo
|
||||||
|
int getTextWidthPhysical(const char* text);
|
||||||
|
|
||||||
// Obtiene la altura de la fuente
|
// Obtiene la altura de la fuente
|
||||||
int getTextHeight();
|
int getTextHeight();
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ void ThemeManager::initialize() {
|
|||||||
"Sunset",
|
"Sunset",
|
||||||
"Atardecer",
|
"Atardecer",
|
||||||
255, 140, 60, // Color texto: naranja cálido
|
255, 140, 60, // Color texto: naranja cálido
|
||||||
|
120, 40, 80, // Color fondo notificación: púrpura oscuro (contrasta con naranja)
|
||||||
180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: naranja suave
|
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
|
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: púrpura oscuro
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -33,6 +34,7 @@ void ThemeManager::initialize() {
|
|||||||
"Ocean",
|
"Ocean",
|
||||||
"Océano",
|
"Océano",
|
||||||
80, 200, 255, // Color texto: azul océano
|
80, 200, 255, // Color texto: azul océano
|
||||||
|
20, 50, 90, // Color fondo notificación: azul marino oscuro (contrasta con cian)
|
||||||
100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior: azul cielo
|
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
|
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: azul marino
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -46,6 +48,7 @@ void ThemeManager::initialize() {
|
|||||||
"Neon",
|
"Neon",
|
||||||
"Neón",
|
"Neón",
|
||||||
255, 60, 255, // Color texto: magenta brillante
|
255, 60, 255, // Color texto: magenta brillante
|
||||||
|
60, 0, 80, // Color fondo notificación: púrpura muy oscuro (contrasta con neón)
|
||||||
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: negro azulado
|
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.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -59,6 +62,7 @@ void ThemeManager::initialize() {
|
|||||||
"Forest",
|
"Forest",
|
||||||
"Bosque",
|
"Bosque",
|
||||||
100, 255, 100, // Color texto: verde natural
|
100, 255, 100, // Color texto: verde natural
|
||||||
|
70, 50, 30, // Color fondo notificación: marrón oscuro tierra (contrasta con verde)
|
||||||
144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior: verde claro
|
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
|
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior: marrón tierra
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -72,6 +76,7 @@ void ThemeManager::initialize() {
|
|||||||
"RGB",
|
"RGB",
|
||||||
"RGB",
|
"RGB",
|
||||||
100, 100, 100, // Color texto: gris oscuro
|
100, 100, 100, // Color texto: gris oscuro
|
||||||
|
220, 220, 220, // Color fondo notificación: gris muy claro (contrasta sobre blanco)
|
||||||
1.0f, 1.0f, 1.0f, // Fondo superior: blanco puro
|
1.0f, 1.0f, 1.0f, // Fondo superior: blanco puro
|
||||||
1.0f, 1.0f, 1.0f, // Fondo inferior: blanco puro (sin degradado)
|
1.0f, 1.0f, 1.0f, // Fondo inferior: blanco puro (sin degradado)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -107,6 +112,7 @@ void ThemeManager::initialize() {
|
|||||||
"Monochrome",
|
"Monochrome",
|
||||||
"Monocromo",
|
"Monocromo",
|
||||||
200, 200, 200, // Color texto: gris claro
|
200, 200, 200, // Color texto: gris claro
|
||||||
|
50, 50, 50, // Color fondo notificación: gris medio oscuro (contrasta con texto claro)
|
||||||
20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: gris muy oscuro
|
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
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -120,6 +126,7 @@ void ThemeManager::initialize() {
|
|||||||
"Lavender",
|
"Lavender",
|
||||||
"Lavanda",
|
"Lavanda",
|
||||||
255, 200, 100, // Color texto: amarillo cálido
|
255, 200, 100, // Color texto: amarillo cálido
|
||||||
|
80, 50, 100, // Color fondo notificación: violeta muy oscuro (contrasta con amarillo)
|
||||||
120.0f / 255.0f, 80.0f / 255.0f, 140.0f / 255.0f, // Fondo superior: violeta oscuro
|
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
|
25.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul medianoche
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -133,6 +140,7 @@ void ThemeManager::initialize() {
|
|||||||
"Crimson",
|
"Crimson",
|
||||||
"Carmesí",
|
"Carmesí",
|
||||||
255, 100, 100, // Color texto: rojo claro
|
255, 100, 100, // Color texto: rojo claro
|
||||||
|
80, 10, 10, // Color fondo notificación: rojo muy oscuro (contrasta con texto claro)
|
||||||
40.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: rojo muy oscuro
|
40.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: rojo muy oscuro
|
||||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -146,6 +154,7 @@ void ThemeManager::initialize() {
|
|||||||
"Emerald",
|
"Emerald",
|
||||||
"Esmeralda",
|
"Esmeralda",
|
||||||
100, 255, 100, // Color texto: verde claro
|
100, 255, 100, // Color texto: verde claro
|
||||||
|
10, 80, 10, // Color fondo notificación: verde muy oscuro (contrasta con texto claro)
|
||||||
0.0f / 255.0f, 40.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: verde muy oscuro
|
0.0f / 255.0f, 40.0f / 255.0f, 0.0f / 255.0f, // Fondo superior: verde muy oscuro
|
||||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
@@ -168,6 +177,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
20.0f / 255.0f, 25.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: azul medianoche
|
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
|
10.0f / 255.0f, 10.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||||
|
20, 30, 80, // Color fondo notificación: azul oscuro (noche)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
||||||
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}
|
{95, 95, 145}, {105, 105, 155}, {100, 100, 150}, {115, 115, 165}
|
||||||
@@ -178,6 +188,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
180.0f / 255.0f, 100.0f / 255.0f, 120.0f / 255.0f, // Fondo superior: 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.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo inferior: naranja cálido
|
||||||
|
140, 60, 80, // Color fondo notificación: naranja-rojo oscuro (alba)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
|
{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
|
||||||
{255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}
|
{255, 190, 110}, {255, 170, 95}, {255, 185, 105}, {255, 165, 88}
|
||||||
@@ -188,6 +199,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
255.0f / 255.0f, 240.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: amarillo claro
|
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.0f / 255.0f, 255.0f / 255.0f, 220.0f / 255.0f, // Fondo inferior: amarillo muy claro
|
||||||
|
200, 180, 140, // Color fondo notificación: amarillo oscuro (día)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
|
{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
|
||||||
{255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}
|
{255, 255, 210}, {255, 255, 185}, {255, 255, 205}, {255, 255, 195}
|
||||||
@@ -209,6 +221,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
20.0f / 255.0f, 50.0f / 255.0f, 100.0f / 255.0f, // Fondo superior: azul marino
|
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
|
10.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||||
|
10, 30, 70, // Color fondo notificación: azul muy oscuro (profundidad)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
||||||
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}
|
{65, 105, 185}, {58, 98, 172}, {62, 102, 182}, {52, 92, 168}
|
||||||
@@ -219,6 +232,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
100.0f / 255.0f, 200.0f / 255.0f, 230.0f / 255.0f, // Fondo superior: turquesa claro
|
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
|
50.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo inferior: turquesa medio
|
||||||
|
30, 100, 140, // Color fondo notificación: turquesa oscuro (aguas poco profundas)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
|
{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
|
||||||
{105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}
|
{105, 225, 255}, {98, 218, 248}, {102, 222, 252}, {92, 212, 242}
|
||||||
@@ -240,6 +254,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
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 superior: negro
|
||||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||||
|
30, 30, 30, // Color fondo notificación: gris muy oscuro (apagado)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
||||||
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}
|
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}
|
||||||
@@ -250,6 +265,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: azul oscuro
|
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.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||||
|
60, 0, 80, // Color fondo notificación: púrpura oscuro (neón encendido)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
|
{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
|
||||||
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}
|
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}
|
||||||
@@ -271,6 +287,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
60.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo superior: rojo muy oscuro
|
60.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo superior: rojo muy oscuro
|
||||||
20.0f / 255.0f, 10.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: casi negro
|
20.0f / 255.0f, 10.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: casi negro
|
||||||
|
70, 20, 10, // Color fondo notificación: rojo muy oscuro (brasas)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{120, 40, 20}, {140, 35, 15}, {130, 38, 18}, {125, 42, 22},
|
{120, 40, 20}, {140, 35, 15}, {130, 38, 18}, {125, 42, 22},
|
||||||
{135, 37, 16}, {128, 40, 20}, {132, 39, 19}, {138, 36, 17}
|
{135, 37, 16}, {128, 40, 20}, {132, 39, 19}, {138, 36, 17}
|
||||||
@@ -281,6 +298,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
|
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
|
||||||
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
||||||
|
110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
||||||
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
|
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
|
||||||
@@ -291,6 +309,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
255.0f / 255.0f, 180.0f / 255.0f, 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante
|
255.0f / 255.0f, 180.0f / 255.0f, 80.0f / 255.0f, // Fondo superior: amarillo-naranja brillante
|
||||||
220.0f / 255.0f, 100.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: naranja intenso
|
220.0f / 255.0f, 100.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: naranja intenso
|
||||||
|
160, 80, 30, // Color fondo notificación: naranja oscuro (inferno)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 220, 100}, {255, 200, 80}, {255, 240, 120}, {255, 210, 90},
|
{255, 220, 100}, {255, 200, 80}, {255, 240, 120}, {255, 210, 90},
|
||||||
{255, 230, 110}, {255, 205, 85}, {255, 225, 105}, {255, 215, 95}
|
{255, 230, 110}, {255, 205, 85}, {255, 225, 105}, {255, 215, 95}
|
||||||
@@ -301,6 +320,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
|
180.0f / 255.0f, 80.0f / 255.0f, 20.0f / 255.0f, // Fondo superior: naranja fuerte
|
||||||
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
100.0f / 255.0f, 30.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
||||||
|
110, 40, 10, // Color fondo notificación: naranja-rojo oscuro (llamas)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
||||||
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
|
{255, 150, 15}, {255, 125, 8}, {255, 145, 12}, {255, 135, 18}
|
||||||
@@ -322,6 +342,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
30.0f / 255.0f, 80.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: verde oscuro
|
30.0f / 255.0f, 80.0f / 255.0f, 60.0f / 255.0f, // Fondo superior: verde oscuro
|
||||||
10.0f / 255.0f, 20.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
10.0f / 255.0f, 20.0f / 255.0f, 30.0f / 255.0f, // Fondo inferior: azul muy oscuro
|
||||||
|
15, 50, 40, // Color fondo notificación: verde muy oscuro (aurora verde)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{100, 255, 180}, {80, 240, 160}, {120, 255, 200}, {90, 245, 170},
|
{100, 255, 180}, {80, 240, 160}, {120, 255, 200}, {90, 245, 170},
|
||||||
{110, 255, 190}, {85, 242, 165}, {105, 252, 185}, {95, 248, 175}
|
{110, 255, 190}, {85, 242, 165}, {105, 252, 185}, {95, 248, 175}
|
||||||
@@ -332,6 +353,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
|
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
|
||||||
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
|
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
|
||||||
|
70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
||||||
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
|
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
|
||||||
@@ -342,6 +364,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
60.0f / 255.0f, 180.0f / 255.0f, 220.0f / 255.0f, // Fondo superior: cian brillante
|
60.0f / 255.0f, 180.0f / 255.0f, 220.0f / 255.0f, // Fondo superior: cian brillante
|
||||||
20.0f / 255.0f, 80.0f / 255.0f, 120.0f / 255.0f, // Fondo inferior: azul oscuro
|
20.0f / 255.0f, 80.0f / 255.0f, 120.0f / 255.0f, // Fondo inferior: azul oscuro
|
||||||
|
20, 90, 120, // Color fondo notificación: cian oscuro (aurora cian)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{100, 220, 255}, {80, 200, 240}, {120, 240, 255}, {90, 210, 245},
|
{100, 220, 255}, {80, 200, 240}, {120, 240, 255}, {90, 210, 245},
|
||||||
{110, 230, 255}, {85, 205, 242}, {105, 225, 252}, {95, 215, 248}
|
{110, 230, 255}, {85, 205, 242}, {105, 225, 252}, {95, 215, 248}
|
||||||
@@ -352,6 +375,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
|
120.0f / 255.0f, 60.0f / 255.0f, 180.0f / 255.0f, // Fondo superior: violeta
|
||||||
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
|
40.0f / 255.0f, 20.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: violeta oscuro
|
||||||
|
70, 30, 100, // Color fondo notificación: violeta oscuro (aurora violeta)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
||||||
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
|
{210, 110, 255}, {185, 85, 242}, {205, 105, 252}, {195, 95, 248}
|
||||||
@@ -373,6 +397,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
40.0f / 255.0f, 40.0f / 255.0f, 45.0f / 255.0f, // Fondo superior: gris oscuro
|
40.0f / 255.0f, 40.0f / 255.0f, 45.0f / 255.0f, // Fondo superior: gris oscuro
|
||||||
20.0f / 255.0f, 15.0f / 255.0f, 15.0f / 255.0f, // Fondo inferior: casi negro
|
20.0f / 255.0f, 15.0f / 255.0f, 15.0f / 255.0f, // Fondo inferior: casi negro
|
||||||
|
50, 50, 55, // Color fondo notificación: gris oscuro (ceniza)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{80, 80, 90}, {75, 75, 85}, {85, 85, 95}, {78, 78, 88},
|
{80, 80, 90}, {75, 75, 85}, {85, 85, 95}, {78, 78, 88},
|
||||||
{82, 82, 92}, {76, 76, 86}, {84, 84, 94}, {79, 79, 89}
|
{82, 82, 92}, {76, 76, 86}, {84, 84, 94}, {79, 79, 89}
|
||||||
@@ -383,6 +408,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
180.0f / 255.0f, 60.0f / 255.0f, 30.0f / 255.0f, // Fondo superior: naranja-rojo
|
180.0f / 255.0f, 60.0f / 255.0f, 30.0f / 255.0f, // Fondo superior: naranja-rojo
|
||||||
80.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
80.0f / 255.0f, 20.0f / 255.0f, 10.0f / 255.0f, // Fondo inferior: rojo oscuro
|
||||||
|
120, 30, 15, // Color fondo notificación: naranja-rojo oscuro (erupción)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 80, 40}, {255, 100, 50}, {255, 70, 35}, {255, 90, 45},
|
{255, 80, 40}, {255, 100, 50}, {255, 70, 35}, {255, 90, 45},
|
||||||
{255, 75, 38}, {255, 95, 48}, {255, 85, 42}, {255, 78, 40}
|
{255, 75, 38}, {255, 95, 48}, {255, 85, 42}, {255, 78, 40}
|
||||||
@@ -393,6 +419,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
220.0f / 255.0f, 120.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: naranja brillante
|
220.0f / 255.0f, 120.0f / 255.0f, 40.0f / 255.0f, // Fondo superior: naranja brillante
|
||||||
180.0f / 255.0f, 60.0f / 255.0f, 20.0f / 255.0f, // Fondo inferior: naranja-rojo
|
180.0f / 255.0f, 60.0f / 255.0f, 20.0f / 255.0f, // Fondo inferior: naranja-rojo
|
||||||
|
150, 70, 25, // Color fondo notificación: naranja oscuro (lava)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{255, 180, 80}, {255, 200, 100}, {255, 170, 70}, {255, 190, 90},
|
{255, 180, 80}, {255, 200, 100}, {255, 170, 70}, {255, 190, 90},
|
||||||
{255, 175, 75}, {255, 195, 95}, {255, 185, 85}, {255, 178, 78}
|
{255, 175, 75}, {255, 195, 95}, {255, 185, 85}, {255, 178, 78}
|
||||||
@@ -403,6 +430,7 @@ void ThemeManager::initialize() {
|
|||||||
{
|
{
|
||||||
100.0f / 255.0f, 80.0f / 255.0f, 70.0f / 255.0f, // Fondo superior: gris-naranja
|
100.0f / 255.0f, 80.0f / 255.0f, 70.0f / 255.0f, // Fondo superior: gris-naranja
|
||||||
50.0f / 255.0f, 40.0f / 255.0f, 35.0f / 255.0f, // Fondo inferior: gris oscuro
|
50.0f / 255.0f, 40.0f / 255.0f, 35.0f / 255.0f, // Fondo inferior: gris oscuro
|
||||||
|
80, 60, 50, // Color fondo notificación: gris-naranja oscuro (enfriamiento)
|
||||||
std::vector<Color>{
|
std::vector<Color>{
|
||||||
{150, 120, 100}, {140, 110, 90}, {160, 130, 110}, {145, 115, 95},
|
{150, 120, 100}, {140, 110, 90}, {160, 130, 110}, {145, 115, 95},
|
||||||
{155, 125, 105}, {142, 112, 92}, {158, 128, 108}, {148, 118, 98}
|
{155, 125, 105}, {142, 112, 92}, {158, 128, 108}, {148, 118, 98}
|
||||||
@@ -567,6 +595,22 @@ void ThemeManager::getCurrentThemeTextColor(int& r, int& g, int& b) const {
|
|||||||
b = static_cast<int>(lerp(static_cast<float>(source_snapshot_->text_color_b), static_cast<float>(target_b), transition_progress_));
|
b = static_cast<int>(lerp(static_cast<float>(source_snapshot_->text_color_b), static_cast<float>(target_b), transition_progress_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThemeManager::getCurrentNotificationBackgroundColor(int& r, int& g, int& b) const {
|
||||||
|
if (!transitioning_ || !source_snapshot_) {
|
||||||
|
// Sin transición: color directo del tema activo
|
||||||
|
themes_[current_theme_index_]->getNotificationBackgroundColor(r, g, b);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 3: Con transición: LERP entre snapshot origen y tema destino
|
||||||
|
int target_r, target_g, target_b;
|
||||||
|
themes_[current_theme_index_]->getNotificationBackgroundColor(target_r, target_g, target_b);
|
||||||
|
|
||||||
|
r = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_r), static_cast<float>(target_r), transition_progress_));
|
||||||
|
g = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_g), static_cast<float>(target_g), transition_progress_));
|
||||||
|
b = static_cast<int>(lerp(static_cast<float>(source_snapshot_->notif_bg_b), static_cast<float>(target_b), transition_progress_));
|
||||||
|
}
|
||||||
|
|
||||||
Color ThemeManager::getInitialBallColor(int random_index) const {
|
Color ThemeManager::getInitialBallColor(int random_index) const {
|
||||||
// Obtener color inicial del tema activo (progress = 0.0f)
|
// Obtener color inicial del tema activo (progress = 0.0f)
|
||||||
return themes_[current_theme_index_]->getBallColor(random_index, 0.0f);
|
return themes_[current_theme_index_]->getBallColor(random_index, 0.0f);
|
||||||
@@ -588,6 +632,10 @@ std::unique_ptr<ThemeSnapshot> ThemeManager::captureCurrentSnapshot() const {
|
|||||||
themes_[current_theme_index_]->getTextColor(
|
themes_[current_theme_index_]->getTextColor(
|
||||||
snapshot->text_color_r, snapshot->text_color_g, snapshot->text_color_b);
|
snapshot->text_color_r, snapshot->text_color_g, snapshot->text_color_b);
|
||||||
|
|
||||||
|
// Capturar color de fondo de notificaciones
|
||||||
|
themes_[current_theme_index_]->getNotificationBackgroundColor(
|
||||||
|
snapshot->notif_bg_r, snapshot->notif_bg_g, snapshot->notif_bg_b);
|
||||||
|
|
||||||
// Capturar nombres
|
// Capturar nombres
|
||||||
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
|
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
|
||||||
snapshot->name_es = themes_[current_theme_index_]->getNameES();
|
snapshot->name_es = themes_[current_theme_index_]->getNameES();
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class ThemeManager {
|
|||||||
const char* getCurrentThemeNameEN() const;
|
const char* getCurrentThemeNameEN() const;
|
||||||
const char* getCurrentThemeNameES() const;
|
const char* getCurrentThemeNameES() const;
|
||||||
void getCurrentThemeTextColor(int& r, int& g, int& b) const;
|
void getCurrentThemeTextColor(int& r, int& g, int& b) const;
|
||||||
|
void getCurrentNotificationBackgroundColor(int& r, int& g, int& b) const;
|
||||||
|
|
||||||
// Obtener color inicial para nuevas pelotas (usado en initBalls)
|
// Obtener color inicial para nuevas pelotas (usado en initBalls)
|
||||||
Color getInitialBallColor(int random_index) const;
|
Color getInitialBallColor(int random_index) const;
|
||||||
|
|||||||
@@ -120,3 +120,16 @@ void DynamicTheme::getBackgroundColors(float progress,
|
|||||||
bg = lerp(current_kf.bg_bottom_g, target_kf.bg_bottom_g, 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);
|
bb = lerp(current_kf.bg_bottom_b, target_kf.bg_bottom_b, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DynamicTheme::getNotificationBackgroundColor(int& r, int& g, int& b) const {
|
||||||
|
// Obtener keyframes actual y objetivo
|
||||||
|
const auto& current_kf = keyframes_[current_keyframe_index_];
|
||||||
|
const auto& target_kf = keyframes_[target_keyframe_index_];
|
||||||
|
|
||||||
|
// Interpolar color de fondo de notificación usando progreso interno
|
||||||
|
float t = transition_progress_;
|
||||||
|
|
||||||
|
r = static_cast<int>(lerp(static_cast<float>(current_kf.notif_bg_r), static_cast<float>(target_kf.notif_bg_r), t));
|
||||||
|
g = static_cast<int>(lerp(static_cast<float>(current_kf.notif_bg_g), static_cast<float>(target_kf.notif_bg_g), t));
|
||||||
|
b = static_cast<int>(lerp(static_cast<float>(current_kf.notif_bg_b), static_cast<float>(target_kf.notif_bg_b), t));
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class DynamicTheme : public Theme {
|
|||||||
g = text_g_;
|
g = text_g_;
|
||||||
b = text_b_;
|
b = text_b_;
|
||||||
}
|
}
|
||||||
|
void getNotificationBackgroundColor(int& r, int& g, int& b) const override;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// CORE: OBTENER COLORES (interpolados)
|
// CORE: OBTENER COLORES (interpolados)
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
|
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
|
||||||
int text_r, int text_g, int text_b,
|
int text_r, int text_g, int text_b,
|
||||||
|
int notif_bg_r, int notif_bg_g, int notif_bg_b,
|
||||||
float bg_top_r, float bg_top_g, float bg_top_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,
|
float bg_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||||
std::vector<Color> ball_colors)
|
std::vector<Color> ball_colors)
|
||||||
: name_en_(name_en),
|
: name_en_(name_en),
|
||||||
name_es_(name_es),
|
name_es_(name_es),
|
||||||
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
||||||
|
notif_bg_r_(notif_bg_r), notif_bg_g_(notif_bg_g), notif_bg_b_(notif_bg_b),
|
||||||
bg_top_r_(bg_top_r), bg_top_g_(bg_top_g), bg_top_b_(bg_top_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),
|
bg_bottom_r_(bg_bottom_r), bg_bottom_g_(bg_bottom_g), bg_bottom_b_(bg_bottom_b),
|
||||||
ball_colors_(std::move(ball_colors)) {
|
ball_colors_(std::move(ball_colors)) {
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ class StaticTheme : public Theme {
|
|||||||
* @param name_en: Nombre en inglés
|
* @param name_en: Nombre en inglés
|
||||||
* @param name_es: Nombre en español
|
* @param name_es: Nombre en español
|
||||||
* @param text_r, text_g, text_b: Color de texto UI
|
* @param text_r, text_g, text_b: Color de texto UI
|
||||||
|
* @param notif_bg_r, notif_bg_g, notif_bg_b: Color de fondo de notificaciones
|
||||||
* @param bg_top_r, bg_top_g, bg_top_b: Color superior de fondo
|
* @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 bg_bottom_r, bg_bottom_g, bg_bottom_b: Color inferior de fondo
|
||||||
* @param ball_colors: Paleta de colores para pelotas
|
* @param ball_colors: Paleta de colores para pelotas
|
||||||
*/
|
*/
|
||||||
StaticTheme(const char* name_en, const char* name_es,
|
StaticTheme(const char* name_en, const char* name_es,
|
||||||
int text_r, int text_g, int text_b,
|
int text_r, int text_g, int text_b,
|
||||||
|
int notif_bg_r, int notif_bg_g, int notif_bg_b,
|
||||||
float bg_top_r, float bg_top_g, float bg_top_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,
|
float bg_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||||
std::vector<Color> ball_colors);
|
std::vector<Color> ball_colors);
|
||||||
@@ -46,6 +48,11 @@ class StaticTheme : public Theme {
|
|||||||
g = text_g_;
|
g = text_g_;
|
||||||
b = text_b_;
|
b = text_b_;
|
||||||
}
|
}
|
||||||
|
void getNotificationBackgroundColor(int& r, int& g, int& b) const override {
|
||||||
|
r = notif_bg_r_;
|
||||||
|
g = notif_bg_g_;
|
||||||
|
b = notif_bg_b_;
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// CORE: OBTENER COLORES
|
// CORE: OBTENER COLORES
|
||||||
@@ -66,6 +73,7 @@ class StaticTheme : public Theme {
|
|||||||
std::string name_en_;
|
std::string name_en_;
|
||||||
std::string name_es_;
|
std::string name_es_;
|
||||||
int text_r_, text_g_, text_b_;
|
int text_r_, text_g_, text_b_;
|
||||||
|
int notif_bg_r_, notif_bg_g_, notif_bg_b_;
|
||||||
float bg_top_r_, bg_top_g_, bg_top_b_;
|
float bg_top_r_, bg_top_g_, bg_top_b_;
|
||||||
float bg_bottom_r_, bg_bottom_g_, bg_bottom_b_;
|
float bg_bottom_r_, bg_bottom_g_, bg_bottom_b_;
|
||||||
std::vector<Color> ball_colors_;
|
std::vector<Color> ball_colors_;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class Theme {
|
|||||||
virtual const char* getNameEN() const = 0;
|
virtual const char* getNameEN() const = 0;
|
||||||
virtual const char* getNameES() const = 0;
|
virtual const char* getNameES() const = 0;
|
||||||
virtual void getTextColor(int& r, int& g, int& b) const = 0;
|
virtual void getTextColor(int& r, int& g, int& b) const = 0;
|
||||||
|
virtual void getNotificationBackgroundColor(int& r, int& g, int& b) const = 0;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// CORE: OBTENER COLORES (polimórfico)
|
// CORE: OBTENER COLORES (polimórfico)
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ struct ThemeSnapshot {
|
|||||||
// Color de texto UI
|
// Color de texto UI
|
||||||
int text_color_r, text_color_g, text_color_b;
|
int text_color_r, text_color_g, text_color_b;
|
||||||
|
|
||||||
|
// Color de fondo de notificaciones
|
||||||
|
int notif_bg_r, notif_bg_g, notif_bg_b;
|
||||||
|
|
||||||
// Nombres del tema (para mostrar "SOURCE → TARGET" durante transición)
|
// Nombres del tema (para mostrar "SOURCE → TARGET" durante transición)
|
||||||
std::string name_en;
|
std::string name_en;
|
||||||
std::string name_es;
|
std::string name_es;
|
||||||
|
|||||||
246
source/ui/notifier.cpp
Normal file
246
source/ui/notifier.cpp
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
#include "notifier.h"
|
||||||
|
#include "../text/textrenderer.h"
|
||||||
|
#include "../theme_manager.h"
|
||||||
|
#include "../defines.h"
|
||||||
|
#include "../utils/easing_functions.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
Notifier::Notifier()
|
||||||
|
: renderer_(nullptr)
|
||||||
|
, text_renderer_(nullptr)
|
||||||
|
, theme_manager_(nullptr)
|
||||||
|
, window_width_(0)
|
||||||
|
, window_height_(0)
|
||||||
|
, current_notification_(nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Notifier::~Notifier() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height) {
|
||||||
|
renderer_ = renderer;
|
||||||
|
text_renderer_ = text_renderer;
|
||||||
|
theme_manager_ = theme_manager;
|
||||||
|
window_width_ = window_width;
|
||||||
|
window_height_ = window_height;
|
||||||
|
return (renderer_ != nullptr && text_renderer_ != nullptr && theme_manager_ != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::updateWindowSize(int window_width, int window_height) {
|
||||||
|
window_width_ = window_width;
|
||||||
|
window_height_ = window_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::show(const std::string& text, Uint64 duration) {
|
||||||
|
if (text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usar duración default si no se especifica
|
||||||
|
if (duration == 0) {
|
||||||
|
duration = NOTIFICATION_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NUEVO: Limpiar notificación actual y cola (solo mostrar la última)
|
||||||
|
// Si hay una notificación activa, destruirla inmediatamente
|
||||||
|
current_notification_.reset();
|
||||||
|
|
||||||
|
// Vaciar cola completa (descartar notificaciones pendientes)
|
||||||
|
while (!notification_queue_.empty()) {
|
||||||
|
notification_queue_.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear nueva notificación
|
||||||
|
Notification notif;
|
||||||
|
notif.text = text;
|
||||||
|
notif.created_time = SDL_GetTicks();
|
||||||
|
notif.duration = duration;
|
||||||
|
notif.state = NotificationState::SLIDING_IN;
|
||||||
|
notif.alpha = 1.0f;
|
||||||
|
notif.y_offset = -50.0f; // Comienza 50px arriba (fuera de pantalla)
|
||||||
|
// NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render()
|
||||||
|
|
||||||
|
// Activar inmediatamente como notificación actual (sin esperar en cola)
|
||||||
|
current_notification_ = std::make_unique<Notification>(notif);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::update(Uint64 current_time) {
|
||||||
|
// Activar siguiente notificación si no hay ninguna activa
|
||||||
|
if (!current_notification_ && !notification_queue_.empty()) {
|
||||||
|
processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar notificación actual
|
||||||
|
if (current_notification_) {
|
||||||
|
Uint64 elapsed = current_time - current_notification_->created_time;
|
||||||
|
|
||||||
|
switch (current_notification_->state) {
|
||||||
|
case NotificationState::SLIDING_IN: {
|
||||||
|
// Animación de entrada (NOTIFICATION_SLIDE_TIME ms)
|
||||||
|
if (elapsed < NOTIFICATION_SLIDE_TIME) {
|
||||||
|
float progress = static_cast<float>(elapsed) / static_cast<float>(NOTIFICATION_SLIDE_TIME);
|
||||||
|
float eased = Easing::easeOutBack(progress); // Efecto con ligero overshoot
|
||||||
|
current_notification_->y_offset = -50.0f + (50.0f * eased); // De -50 a 0
|
||||||
|
} else {
|
||||||
|
// Transición a VISIBLE
|
||||||
|
current_notification_->y_offset = 0.0f;
|
||||||
|
current_notification_->state = NotificationState::VISIBLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NotificationState::VISIBLE: {
|
||||||
|
// Esperar hasta que se cumpla la duración
|
||||||
|
Uint64 visible_time = current_notification_->duration - NOTIFICATION_FADE_TIME;
|
||||||
|
if (elapsed >= visible_time) {
|
||||||
|
current_notification_->state = NotificationState::FADING_OUT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NotificationState::FADING_OUT: {
|
||||||
|
// Animación de salida (NOTIFICATION_FADE_TIME ms)
|
||||||
|
Uint64 fade_start = current_notification_->duration - NOTIFICATION_FADE_TIME;
|
||||||
|
Uint64 fade_elapsed = elapsed - fade_start;
|
||||||
|
|
||||||
|
if (fade_elapsed < NOTIFICATION_FADE_TIME) {
|
||||||
|
float progress = static_cast<float>(fade_elapsed) / static_cast<float>(NOTIFICATION_FADE_TIME);
|
||||||
|
float eased = Easing::easeInQuad(progress); // Fade suave
|
||||||
|
current_notification_->alpha = 1.0f - eased;
|
||||||
|
} else {
|
||||||
|
// Transición a DONE
|
||||||
|
current_notification_->alpha = 0.0f;
|
||||||
|
current_notification_->state = NotificationState::DONE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NotificationState::DONE: {
|
||||||
|
// Eliminar notificación actual
|
||||||
|
current_notification_.reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::render() {
|
||||||
|
if (!current_notification_ || !text_renderer_ || !renderer_ || !theme_manager_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener colores DINÁMICOS desde ThemeManager (incluye LERP automático)
|
||||||
|
int text_r, text_g, text_b;
|
||||||
|
theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||||
|
SDL_Color text_color = {
|
||||||
|
static_cast<Uint8>(text_r),
|
||||||
|
static_cast<Uint8>(text_g),
|
||||||
|
static_cast<Uint8>(text_b),
|
||||||
|
static_cast<Uint8>(current_notification_->alpha * 255.0f)
|
||||||
|
};
|
||||||
|
|
||||||
|
int bg_r, bg_g, bg_b;
|
||||||
|
theme_manager_->getCurrentNotificationBackgroundColor(bg_r, bg_g, bg_b);
|
||||||
|
SDL_Color bg_color = {
|
||||||
|
static_cast<Uint8>(bg_r),
|
||||||
|
static_cast<Uint8>(bg_g),
|
||||||
|
static_cast<Uint8>(bg_b),
|
||||||
|
255
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calcular dimensiones del texto en píxeles FÍSICOS
|
||||||
|
// IMPORTANTE: Usar getTextWidthPhysical() en lugar de getTextWidth()
|
||||||
|
// para obtener el ancho REAL de la fuente (sin escalado lógico)
|
||||||
|
int text_width = text_renderer_->getTextWidthPhysical(current_notification_->text.c_str());
|
||||||
|
int text_height = text_renderer_->getTextHeight();
|
||||||
|
|
||||||
|
// Calcular dimensiones del fondo con padding (en píxeles físicos)
|
||||||
|
int bg_width = text_width + (NOTIFICATION_PADDING * 2);
|
||||||
|
int bg_height = text_height + (NOTIFICATION_PADDING * 2);
|
||||||
|
|
||||||
|
// Centrar en la ventana FÍSICA (no usar viewport lógico)
|
||||||
|
// CRÍTICO: Como renderizamos en píxeles físicos absolutos (bypass de presentación lógica),
|
||||||
|
// debemos centrar usando dimensiones físicas, no el viewport lógico de SDL
|
||||||
|
int x = (window_width_ / 2) - (bg_width / 2);
|
||||||
|
int y = NOTIFICATION_TOP_MARGIN + static_cast<int>(current_notification_->y_offset);
|
||||||
|
|
||||||
|
// Renderizar fondo semitransparente (con bypass de presentación lógica)
|
||||||
|
float bg_alpha = current_notification_->alpha * NOTIFICATION_BG_ALPHA;
|
||||||
|
renderBackground(x, y, bg_width, bg_height, bg_alpha, bg_color);
|
||||||
|
|
||||||
|
// Renderizar texto con alpha usando printAbsolute (tamaño físico fijo)
|
||||||
|
int text_x = x + NOTIFICATION_PADDING;
|
||||||
|
int text_y = y + NOTIFICATION_PADDING;
|
||||||
|
|
||||||
|
// printAbsolute() ya maneja el bypass de presentación lógica internamente
|
||||||
|
text_renderer_->printAbsolute(text_x, text_y, current_notification_->text.c_str(), text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color) {
|
||||||
|
if (!renderer_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener viewport ANTES de deshabilitar presentación lógica
|
||||||
|
// En modo letterbox (F3), SDL crea un viewport con offset para centrar la imagen
|
||||||
|
SDL_Rect viewport;
|
||||||
|
SDL_GetRenderViewport(renderer_, &viewport);
|
||||||
|
|
||||||
|
// Crear rectángulo para el fondo (en coordenadas físicas)
|
||||||
|
// Aplicar offset del viewport para que el fondo se pinte dentro del área visible
|
||||||
|
SDL_FRect bg_rect;
|
||||||
|
bg_rect.x = static_cast<float>(x + viewport.x);
|
||||||
|
bg_rect.y = static_cast<float>(y + viewport.y);
|
||||||
|
bg_rect.w = static_cast<float>(width);
|
||||||
|
bg_rect.h = static_cast<float>(height);
|
||||||
|
|
||||||
|
// Color del tema con alpha
|
||||||
|
Uint8 bg_alpha = static_cast<Uint8>(alpha * 255.0f);
|
||||||
|
SDL_SetRenderDrawColor(renderer_, bg_color.r, bg_color.g, bg_color.b, bg_alpha);
|
||||||
|
|
||||||
|
// Habilitar blending para transparencia
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
|
// CRÍTICO: Deshabilitar presentación lógica para renderizar en píxeles físicos absolutos
|
||||||
|
// (igual que printAbsolute() en TextRenderer)
|
||||||
|
int logical_w = 0, logical_h = 0;
|
||||||
|
SDL_RendererLogicalPresentation presentation_mode;
|
||||||
|
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode);
|
||||||
|
|
||||||
|
// Renderizar sin presentación lógica (coordenadas físicas absolutas con offset de viewport)
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
|
||||||
|
SDL_RenderFillRect(renderer_, &bg_rect);
|
||||||
|
|
||||||
|
// Restaurar presentación lógica
|
||||||
|
SDL_SetRenderLogicalPresentation(renderer_, logical_w, logical_h, presentation_mode);
|
||||||
|
|
||||||
|
// Restaurar blend mode
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::isActive() const {
|
||||||
|
return (current_notification_ != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::clear() {
|
||||||
|
// Vaciar cola
|
||||||
|
while (!notification_queue_.empty()) {
|
||||||
|
notification_queue_.pop();
|
||||||
|
}
|
||||||
|
// Eliminar notificación actual
|
||||||
|
current_notification_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::processQueue() {
|
||||||
|
if (notification_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sacar siguiente notificación de la cola
|
||||||
|
Notification next_notif = notification_queue_.front();
|
||||||
|
notification_queue_.pop();
|
||||||
|
|
||||||
|
// Activarla como notificación actual
|
||||||
|
current_notification_ = std::make_unique<Notification>(next_notif);
|
||||||
|
}
|
||||||
111
source/ui/notifier.h
Normal file
111
source/ui/notifier.h
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <string>
|
||||||
|
#include <queue>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class TextRenderer;
|
||||||
|
class ThemeManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sistema de notificaciones estilo iOS/Android
|
||||||
|
*
|
||||||
|
* Maneja notificaciones temporales con animaciones suaves:
|
||||||
|
* - Slide-in desde arriba
|
||||||
|
* - Fade-out al desaparecer
|
||||||
|
* - Cola FIFO de mensajes
|
||||||
|
* - Fondo semitransparente
|
||||||
|
* - Texto de tamaño fijo independiente de resolución
|
||||||
|
*/
|
||||||
|
class Notifier {
|
||||||
|
public:
|
||||||
|
enum class NotificationState {
|
||||||
|
SLIDING_IN, // Animación de entrada desde arriba
|
||||||
|
VISIBLE, // Visible estático
|
||||||
|
FADING_OUT, // Animación de salida (fade)
|
||||||
|
DONE // Completado, listo para eliminar
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Notification {
|
||||||
|
std::string text;
|
||||||
|
Uint64 created_time;
|
||||||
|
Uint64 duration;
|
||||||
|
NotificationState state;
|
||||||
|
float alpha; // Opacidad 0.0-1.0
|
||||||
|
float y_offset; // Offset Y para animación slide (píxeles)
|
||||||
|
// NOTA: Los colores se obtienen dinámicamente desde ThemeManager en render()
|
||||||
|
};
|
||||||
|
|
||||||
|
Notifier();
|
||||||
|
~Notifier();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inicializa el notifier con un TextRenderer y ThemeManager
|
||||||
|
* @param renderer SDL renderer para dibujar
|
||||||
|
* @param text_renderer TextRenderer configurado con tamaño absoluto
|
||||||
|
* @param theme_manager ThemeManager para obtener colores dinámicos con LERP
|
||||||
|
* @param window_width Ancho de ventana física
|
||||||
|
* @param window_height Alto de ventana física
|
||||||
|
* @return true si inicialización exitosa
|
||||||
|
*/
|
||||||
|
bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, ThemeManager* theme_manager, int window_width, int window_height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Actualiza las dimensiones de la ventana (llamar en resize)
|
||||||
|
* @param window_width Nuevo ancho de ventana física
|
||||||
|
* @param window_height Nuevo alto de ventana física
|
||||||
|
*/
|
||||||
|
void updateWindowSize(int window_width, int window_height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Muestra una nueva notificación
|
||||||
|
* @param text Texto a mostrar
|
||||||
|
* @param duration Duración en milisegundos (0 = usar default)
|
||||||
|
* @note Los colores se obtienen dinámicamente desde ThemeManager cada frame
|
||||||
|
*/
|
||||||
|
void show(const std::string& text, Uint64 duration = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Actualiza las animaciones de notificaciones
|
||||||
|
* @param current_time Tiempo actual en ms (SDL_GetTicks())
|
||||||
|
*/
|
||||||
|
void update(Uint64 current_time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renderiza la notificación activa
|
||||||
|
*/
|
||||||
|
void render();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verifica si hay una notificación activa (visible)
|
||||||
|
* @return true si hay notificación mostrándose
|
||||||
|
*/
|
||||||
|
bool isActive() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Limpia todas las notificaciones pendientes
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Renderer* renderer_;
|
||||||
|
TextRenderer* text_renderer_;
|
||||||
|
ThemeManager* theme_manager_; // Gestor de temas para obtener colores dinámicos con LERP
|
||||||
|
int window_width_;
|
||||||
|
int window_height_;
|
||||||
|
|
||||||
|
std::queue<Notification> notification_queue_;
|
||||||
|
std::unique_ptr<Notification> current_notification_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Procesa la cola y activa la siguiente notificación si es posible
|
||||||
|
*/
|
||||||
|
void processQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dibuja el fondo semitransparente de la notificación
|
||||||
|
*/
|
||||||
|
void renderBackground(int x, int y, int width, int height, float alpha, SDL_Color bg_color);
|
||||||
|
};
|
||||||
213
source/utils/easing_functions.h
Normal file
213
source/utils/easing_functions.h
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file easing_functions.h
|
||||||
|
* @brief Funciones de suavizado (easing) para animaciones
|
||||||
|
*
|
||||||
|
* Colección de funciones matemáticas para interpolar valores de forma suave.
|
||||||
|
* Todas las funciones toman un parámetro t (0.0 - 1.0) y devuelven un valor interpolado.
|
||||||
|
*
|
||||||
|
* Uso típico:
|
||||||
|
* float progress = elapsed_time / total_duration; // 0.0 a 1.0
|
||||||
|
* float eased = easeOutCubic(progress);
|
||||||
|
* float current_value = start + (end - start) * eased;
|
||||||
|
*
|
||||||
|
* Referencias:
|
||||||
|
* - https://easings.net/
|
||||||
|
* - Robert Penner's Easing Functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace Easing {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interpolación lineal (sin suavizado)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado linealmente
|
||||||
|
*
|
||||||
|
* Uso: Movimiento constante, sin aceleración
|
||||||
|
*/
|
||||||
|
inline float linear(float t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aceleración cuadrática (slow start)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con aceleración cuadrática
|
||||||
|
*
|
||||||
|
* Uso: Inicio lento que acelera
|
||||||
|
*/
|
||||||
|
inline float easeInQuad(float t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Desaceleración cuadrática (slow end)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con desaceleración cuadrática
|
||||||
|
*
|
||||||
|
* Uso: Llegada suave, objetos que frenan
|
||||||
|
*/
|
||||||
|
inline float easeOutQuad(float t) {
|
||||||
|
return t * (2.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aceleración y desaceleración cuadrática (slow start & end)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con aceleración y desaceleración
|
||||||
|
*
|
||||||
|
* Uso: Movimiento suave en ambos extremos
|
||||||
|
*/
|
||||||
|
inline float easeInOutQuad(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aceleración cúbica (slower start)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con aceleración cúbica
|
||||||
|
*
|
||||||
|
* Uso: Inicio muy lento, aceleración pronunciada
|
||||||
|
*/
|
||||||
|
inline float easeInCubic(float t) {
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Desaceleración cúbica (slower end)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con desaceleración cúbica
|
||||||
|
*
|
||||||
|
* Uso: Frenado suave y natural
|
||||||
|
*/
|
||||||
|
inline float easeOutCubic(float t) {
|
||||||
|
float f = t - 1.0f;
|
||||||
|
return f * f * f + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aceleración y desaceleración cúbica
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con curva cúbica
|
||||||
|
*
|
||||||
|
* Uso: Animaciones muy suaves en ambos extremos
|
||||||
|
*/
|
||||||
|
inline float easeInOutCubic(float t) {
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 4.0f * t * t * t;
|
||||||
|
} else {
|
||||||
|
float f = (2.0f * t - 2.0f);
|
||||||
|
return 0.5f * f * f * f + 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Efecto elástico con rebote al final
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con rebote elástico
|
||||||
|
*
|
||||||
|
* Uso: Elementos UI que "rebotan" al llegar, notificaciones
|
||||||
|
* ⚠️ Puede sobrepasar el valor 1.0 temporalmente (overshoot)
|
||||||
|
*/
|
||||||
|
inline float easeOutElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
|
||||||
|
constexpr float p = 0.3f;
|
||||||
|
constexpr float s = p / 4.0f;
|
||||||
|
|
||||||
|
return powf(2.0f, -10.0f * t) * sinf((t - s) * (2.0f * 3.14159265358979323846f) / p) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sobrepaso suave al final (back easing)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con sobrepaso
|
||||||
|
*
|
||||||
|
* Uso: Elementos que "se pasan" levemente y vuelven, efecto cartoon
|
||||||
|
* ⚠️ Puede sobrepasar el valor 1.0 temporalmente (overshoot ~10%)
|
||||||
|
*/
|
||||||
|
inline float easeOutBack(float t) {
|
||||||
|
constexpr float c1 = 1.70158f;
|
||||||
|
constexpr float c3 = c1 + 1.0f;
|
||||||
|
|
||||||
|
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sobrepaso suave al inicio (back easing)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con retroceso inicial
|
||||||
|
*
|
||||||
|
* Uso: Elementos que "retroceden" antes de avanzar
|
||||||
|
* ⚠️ Puede generar valores negativos al inicio (undershoot ~10%)
|
||||||
|
*/
|
||||||
|
inline float easeInBack(float t) {
|
||||||
|
constexpr float c1 = 1.70158f;
|
||||||
|
constexpr float c3 = c1 + 1.0f;
|
||||||
|
|
||||||
|
return c3 * t * t * t - c1 * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rebote físico al final (bounce effect)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con rebotes
|
||||||
|
*
|
||||||
|
* Uso: Objetos que caen y rebotan, físicas cartoon
|
||||||
|
*/
|
||||||
|
inline float easeOutBounce(float t) {
|
||||||
|
constexpr float n1 = 7.5625f;
|
||||||
|
constexpr float d1 = 2.75f;
|
||||||
|
|
||||||
|
if (t < 1.0f / d1) {
|
||||||
|
return n1 * t * t;
|
||||||
|
} else if (t < 2.0f / d1) {
|
||||||
|
t -= 1.5f / d1;
|
||||||
|
return n1 * t * t + 0.75f;
|
||||||
|
} else if (t < 2.5f / d1) {
|
||||||
|
t -= 2.25f / d1;
|
||||||
|
return n1 * t * t + 0.9375f;
|
||||||
|
} else {
|
||||||
|
t -= 2.625f / d1;
|
||||||
|
return n1 * t * t + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aceleración exponencial (muy dramática)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado exponencialmente
|
||||||
|
*
|
||||||
|
* Uso: Efectos dramáticos, zoom in rápido
|
||||||
|
*/
|
||||||
|
inline float easeInExpo(float t) {
|
||||||
|
return t == 0.0f ? 0.0f : powf(2.0f, 10.0f * (t - 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Desaceleración exponencial (muy dramática)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado exponencialmente
|
||||||
|
*
|
||||||
|
* Uso: Fade outs dramáticos, zoom out rápido
|
||||||
|
*/
|
||||||
|
inline float easeOutExpo(float t) {
|
||||||
|
return t == 1.0f ? 1.0f : 1.0f - powf(2.0f, -10.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Curva circular (cuarto de círculo)
|
||||||
|
* @param t Progreso normalizado (0.0 - 1.0)
|
||||||
|
* @return Valor interpolado con curva circular
|
||||||
|
*
|
||||||
|
* Uso: Movimientos muy suaves y naturales
|
||||||
|
*/
|
||||||
|
inline float easeOutCirc(float t) {
|
||||||
|
return sqrtf(1.0f - powf(t - 1.0f, 2.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Easing
|
||||||
Reference in New Issue
Block a user