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()
|
||||
|
||||
# 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")
|
||||
|
||||
# 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²)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
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
|
||||
@@ -65,6 +74,9 @@ struct DynamicThemeKeyframe {
|
||||
float bg_top_r, bg_top_g, bg_top_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
|
||||
std::vector<Color> ball_colors;
|
||||
|
||||
|
||||
@@ -83,6 +83,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
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
|
||||
int window_width = logical_width * 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
|
||||
{
|
||||
// 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;
|
||||
if (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;
|
||||
success = false;
|
||||
} else {
|
||||
// Centrar ventana en pantalla si no está en fullscreen
|
||||
if (!fullscreen) {
|
||||
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
|
||||
// Crear renderizador
|
||||
renderer_ = SDL_CreateRenderer(window_, 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)));
|
||||
|
||||
// Calcular tamaño de fuente escalado según resolución
|
||||
// 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
|
||||
// Inicializar ThemeManager PRIMERO (requerido por Notifier)
|
||||
theme_manager_ = std::make_unique<ThemeManager>();
|
||||
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_);
|
||||
}
|
||||
|
||||
@@ -298,11 +296,14 @@ void Engine::update() {
|
||||
updateShape();
|
||||
}
|
||||
|
||||
// Actualizar texto (sin cambios en la lógica)
|
||||
// Actualizar texto (OBSOLETO: sistema antiguo, se mantiene por compatibilidad temporal)
|
||||
if (show_text_) {
|
||||
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
|
||||
}
|
||||
|
||||
// Actualizar sistema de notificaciones
|
||||
notifier_.update(current_time);
|
||||
|
||||
// Actualizar Modo DEMO (auto-play)
|
||||
updateDemoMode();
|
||||
|
||||
@@ -337,8 +338,12 @@ void Engine::handleEvents() {
|
||||
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeMode(false); // Desactivar figura sin forzar gravedad ON
|
||||
showNotificationForAction("Gravedad Off");
|
||||
} else {
|
||||
switchBallsGravity(); // Toggle normal en modo física
|
||||
// Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON)
|
||||
bool gravity_on = balls_.empty() ? true : (balls_[0]->getGravityForce() != 0.0f);
|
||||
showNotificationForAction(gravity_on ? "Gravedad On" : "Gravedad Off");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -351,6 +356,7 @@ void Engine::handleEvents() {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
changeGravityDirection(GravityDirection::UP);
|
||||
showNotificationForAction("Gravedad Arriba");
|
||||
break;
|
||||
|
||||
case SDLK_DOWN:
|
||||
@@ -361,6 +367,7 @@ void Engine::handleEvents() {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
changeGravityDirection(GravityDirection::DOWN);
|
||||
showNotificationForAction("Gravedad Abajo");
|
||||
break;
|
||||
|
||||
case SDLK_LEFT:
|
||||
@@ -371,6 +378,7 @@ void Engine::handleEvents() {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
changeGravityDirection(GravityDirection::LEFT);
|
||||
showNotificationForAction("Gravedad Izquierda");
|
||||
break;
|
||||
|
||||
case SDLK_RIGHT:
|
||||
@@ -381,6 +389,7 @@ void Engine::handleEvents() {
|
||||
enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
changeGravityDirection(GravityDirection::RIGHT);
|
||||
showNotificationForAction("Gravedad Derecha");
|
||||
break;
|
||||
|
||||
case SDLK_V:
|
||||
@@ -394,43 +403,61 @@ void Engine::handleEvents() {
|
||||
// Toggle Física ↔ Última Figura (antes era C)
|
||||
case SDLK_F:
|
||||
toggleShapeMode();
|
||||
// Mostrar notificación según el modo actual después del toggle
|
||||
if (current_mode_ == SimulationMode::PHYSICS) {
|
||||
showNotificationForAction("Modo Física");
|
||||
} else {
|
||||
// Mostrar nombre de la figura actual (orden debe coincidir con enum ShapeType)
|
||||
// Índices: 0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS, 6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
|
||||
const char* shape_names[] = {"Ninguna", "Esfera", "Cubo", "Hélice", "Toroide", "Lissajous", "Cilindro", "Icosaedro", "Átomo", "Forma PNG"};
|
||||
showNotificationForAction(shape_names[static_cast<int>(current_shape_type_)]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Selección directa de figuras 3D
|
||||
case SDLK_Q:
|
||||
activateShape(ShapeType::SPHERE);
|
||||
showNotificationForAction("Esfera");
|
||||
break;
|
||||
|
||||
case SDLK_W:
|
||||
activateShape(ShapeType::LISSAJOUS);
|
||||
showNotificationForAction("Lissajous");
|
||||
break;
|
||||
|
||||
case SDLK_E:
|
||||
activateShape(ShapeType::HELIX);
|
||||
showNotificationForAction("Hélice");
|
||||
break;
|
||||
|
||||
case SDLK_R:
|
||||
activateShape(ShapeType::TORUS);
|
||||
showNotificationForAction("Toroide");
|
||||
break;
|
||||
|
||||
case SDLK_T:
|
||||
activateShape(ShapeType::CUBE);
|
||||
showNotificationForAction("Cubo");
|
||||
break;
|
||||
|
||||
case SDLK_Y:
|
||||
activateShape(ShapeType::CYLINDER);
|
||||
showNotificationForAction("Cilindro");
|
||||
break;
|
||||
|
||||
case SDLK_U:
|
||||
activateShape(ShapeType::ICOSAHEDRON);
|
||||
showNotificationForAction("Icosaedro");
|
||||
break;
|
||||
|
||||
case SDLK_I:
|
||||
activateShape(ShapeType::ATOM);
|
||||
showNotificationForAction("Átomo");
|
||||
break;
|
||||
|
||||
case SDLK_O:
|
||||
activateShape(ShapeType::PNG_SHAPE);
|
||||
showNotificationForAction("Forma PNG");
|
||||
break;
|
||||
|
||||
// Ciclar temas de color (movido de T a B)
|
||||
@@ -446,13 +473,8 @@ void Engine::handleEvents() {
|
||||
theme_manager_->cycleTheme();
|
||||
}
|
||||
|
||||
// Mostrar nombre del tema (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
// Mostrar notificación con el nombre del tema
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -462,12 +484,7 @@ void Engine::handleEvents() {
|
||||
{
|
||||
int theme_index = (theme_page_ == 0) ? 0 : 10;
|
||||
theme_manager_->switchToTheme(theme_index);
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -476,12 +493,7 @@ void Engine::handleEvents() {
|
||||
{
|
||||
int theme_index = (theme_page_ == 0) ? 1 : 11;
|
||||
theme_manager_->switchToTheme(theme_index);
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -490,12 +502,7 @@ void Engine::handleEvents() {
|
||||
{
|
||||
int theme_index = (theme_page_ == 0) ? 2 : 12;
|
||||
theme_manager_->switchToTheme(theme_index);
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -504,12 +511,7 @@ void Engine::handleEvents() {
|
||||
{
|
||||
int theme_index = (theme_page_ == 0) ? 3 : 13;
|
||||
theme_manager_->switchToTheme(theme_index);
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -518,12 +520,7 @@ void Engine::handleEvents() {
|
||||
{
|
||||
int theme_index = (theme_page_ == 0) ? 4 : 14;
|
||||
theme_manager_->switchToTheme(theme_index);
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -531,12 +528,7 @@ void Engine::handleEvents() {
|
||||
// Solo página 0: MONOCHROME (5)
|
||||
if (theme_page_ == 0) {
|
||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -544,12 +536,7 @@ void Engine::handleEvents() {
|
||||
// Solo página 0: LAVENDER (6)
|
||||
if (theme_page_ == 0) {
|
||||
theme_manager_->switchToTheme(6); // LAVENDER
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -557,12 +544,7 @@ void Engine::handleEvents() {
|
||||
// Solo página 0: CRIMSON (7)
|
||||
if (theme_page_ == 0) {
|
||||
theme_manager_->switchToTheme(7); // CRIMSON
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -570,12 +552,7 @@ void Engine::handleEvents() {
|
||||
// Solo página 0: EMERALD (8)
|
||||
if (theme_page_ == 0) {
|
||||
theme_manager_->switchToTheme(8); // EMERALD
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -583,12 +560,7 @@ void Engine::handleEvents() {
|
||||
// Solo página 0: SUNRISE (9)
|
||||
if (theme_page_ == 0) {
|
||||
theme_manager_->switchToTheme(9); // SUNRISE
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = theme_manager_->getCurrentThemeNameES();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction(theme_manager_->getCurrentThemeNameES());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -596,14 +568,7 @@ void Engine::handleEvents() {
|
||||
case SDLK_KP_ENTER:
|
||||
// Alternar entre página 0 y página 1
|
||||
theme_page_ = (theme_page_ == 0) ? 1 : 0;
|
||||
|
||||
// Mostrar feedback visual (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
text_ = (theme_page_ == 0) ? "Página 1" : "Página 2";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
}
|
||||
showNotificationForAction((theme_page_ == 0) ? "Página 1" : "Página 2");
|
||||
break;
|
||||
|
||||
// Cambio de sprite/textura dinámico
|
||||
@@ -616,11 +581,7 @@ void Engine::handleEvents() {
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
shape_scale_factor_ += SHAPE_SCALE_STEP;
|
||||
clampShapeScale();
|
||||
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
|
||||
int text_width = static_cast<int>(text_.length() * 8);
|
||||
text_pos_ = (current_screen_width_ - text_width) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
show_text_ = true;
|
||||
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -628,74 +589,70 @@ void Engine::handleEvents() {
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
shape_scale_factor_ -= SHAPE_SCALE_STEP;
|
||||
clampShapeScale();
|
||||
text_ = "Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%";
|
||||
int text_width = static_cast<int>(text_.length() * 8);
|
||||
text_pos_ = (current_screen_width_ - text_width) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
show_text_ = true;
|
||||
showNotificationForAction("Escala " + std::to_string(static_cast<int>(shape_scale_factor_ * 100.0f + 0.5f)) + "%");
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_KP_MULTIPLY:
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
shape_scale_factor_ = SHAPE_SCALE_DEFAULT;
|
||||
text_ = "Escala reiniciada (100%)";
|
||||
int text_width = static_cast<int>(text_.length() * 8);
|
||||
text_pos_ = (current_screen_width_ - text_width) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
show_text_ = true;
|
||||
showNotificationForAction("Escala 100%");
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_KP_DIVIDE:
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
depth_zoom_enabled_ = !depth_zoom_enabled_;
|
||||
text_ = depth_zoom_enabled_ ? "Zoom profundidad: On" : "Zoom profundidad: Off";
|
||||
int text_width = static_cast<int>(text_.length() * 8);
|
||||
text_pos_ = (current_screen_width_ - text_width) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
show_text_ = true;
|
||||
showNotificationForAction(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off");
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_1:
|
||||
scenario_ = 0;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("10 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_2:
|
||||
scenario_ = 1;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("50 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_3:
|
||||
scenario_ = 2;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("100 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_4:
|
||||
scenario_ = 3;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("500 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_5:
|
||||
scenario_ = 4;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("1,000 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_6:
|
||||
scenario_ = 5;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("5,000 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_7:
|
||||
scenario_ = 6;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("10,000 Pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_8:
|
||||
scenario_ = 7;
|
||||
initBalls(scenario_);
|
||||
showNotificationForAction("50,000 Pelotas");
|
||||
break;
|
||||
|
||||
// Controles de zoom dinámico (solo si no estamos en fullscreen)
|
||||
@@ -732,22 +689,16 @@ void Engine::handleEvents() {
|
||||
if (event.key.mod & SDL_KMOD_SHIFT) {
|
||||
theme_manager_->pauseDynamic();
|
||||
} else {
|
||||
// D sin Shift = Toggle modo DEMO
|
||||
// D sin Shift = Toggle DEMO ↔ SANDBOX
|
||||
if (current_app_mode_ == AppMode::DEMO) {
|
||||
// Desactivar DEMO → MANUAL
|
||||
setState(AppMode::MANUAL);
|
||||
text_ = "Modo Demo: Off";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
// Ya estamos en DEMO → volver a SANDBOX
|
||||
setState(AppMode::SANDBOX);
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
} else {
|
||||
// Activar DEMO (desde cualquier otro modo)
|
||||
// Estamos en otro modo → ir a DEMO
|
||||
setState(AppMode::DEMO);
|
||||
randomizeOnDemoStart(false);
|
||||
text_ = "Modo Demo: On";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
showNotificationForAction("MODO DEMO");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -755,35 +706,28 @@ void Engine::handleEvents() {
|
||||
// Toggle Modo DEMO LITE (solo física/figuras)
|
||||
case SDLK_L:
|
||||
if (current_app_mode_ == AppMode::DEMO_LITE) {
|
||||
// Desactivar DEMO_LITE → MANUAL
|
||||
setState(AppMode::MANUAL);
|
||||
text_ = "Modo Demo Lite: Off";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
// Ya estamos en DEMO_LITE → volver a SANDBOX
|
||||
setState(AppMode::SANDBOX);
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
} else {
|
||||
// Activar DEMO_LITE (desde cualquier otro modo)
|
||||
// Estamos en otro modo → ir a DEMO_LITE
|
||||
setState(AppMode::DEMO_LITE);
|
||||
randomizeOnDemoStart(true);
|
||||
text_ = "Modo Demo Lite: On";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
showNotificationForAction("MODO DEMO LITE");
|
||||
}
|
||||
break;
|
||||
|
||||
// Toggle Modo LOGO (easter egg - marca de agua)
|
||||
case SDLK_K:
|
||||
toggleLogoMode();
|
||||
// Mostrar texto informativo
|
||||
if (current_app_mode_ == AppMode::LOGO) {
|
||||
text_ = "Modo Logo: On";
|
||||
// Ya estamos en LOGO → volver a SANDBOX
|
||||
exitLogoMode(false);
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
} else {
|
||||
text_ = "Modo Logo: Off";
|
||||
// Estamos en otro modo → ir a LOGO
|
||||
enterLogoMode(false);
|
||||
showNotificationForAction("MODO LOGO");
|
||||
}
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
// 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_) {
|
||||
// Obtener datos del tema actual (delegado a ThemeManager)
|
||||
int text_color_r, text_color_g, text_color_b;
|
||||
@@ -894,7 +842,7 @@ void Engine::render() {
|
||||
int margin = 8;
|
||||
|
||||
// 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
|
||||
// (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
|
||||
|
||||
// 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)
|
||||
if (show_debug_) {
|
||||
// Obtener altura de línea para espaciado dinámico (usando fuente debug)
|
||||
int line_height = text_renderer_debug_.getTextHeight();
|
||||
int margin = 8; // Margen constante
|
||||
int current_y = margin; // Y inicial
|
||||
int margin = 8; // Margen constante en píxeles físicos
|
||||
int current_y = margin; // Y inicial en píxeles físicos
|
||||
|
||||
// Mostrar contador de FPS en esquina superior derecha
|
||||
int fps_text_width = text_renderer_debug_.getTextWidth(fps_text_.c_str());
|
||||
int fps_x = current_screen_width_ - fps_text_width - margin;
|
||||
text_renderer_debug_.print(fps_x, current_y, fps_text_.c_str(), 255, 255, 0); // Amarillo
|
||||
int fps_text_width = text_renderer_debug_.getTextWidthPhysical(fps_text_.c_str());
|
||||
int fps_x = physical_window_width_ - fps_text_width - margin;
|
||||
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
|
||||
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;
|
||||
|
||||
// Debug: Mostrar valores de la primera pelota (si existe)
|
||||
@@ -929,35 +878,35 @@ void Engine::render() {
|
||||
// Línea 1: Gravedad
|
||||
int grav_int = static_cast<int>(balls_[0]->getGravityForce());
|
||||
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;
|
||||
|
||||
// Línea 2: Velocidad Y
|
||||
int vy_int = static_cast<int>(balls_[0]->getVelocityY());
|
||||
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;
|
||||
|
||||
// Línea 3: Estado superficie
|
||||
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;
|
||||
|
||||
// Línea 4: Coeficiente de rebote (loss)
|
||||
float loss_val = balls_[0]->getLossCoefficient();
|
||||
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;
|
||||
|
||||
// Línea 5: Dirección de gravedad
|
||||
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;
|
||||
}
|
||||
|
||||
// Debug: Mostrar tema actual (delegado a ThemeManager)
|
||||
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;
|
||||
|
||||
// Debug: Mostrar modo de simulación actual
|
||||
@@ -969,14 +918,14 @@ void Engine::render() {
|
||||
} else {
|
||||
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;
|
||||
|
||||
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
|
||||
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
|
||||
int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -985,22 +934,25 @@ void Engine::render() {
|
||||
int fixed_y = margin + (line_height * 2); // Tercera fila fija
|
||||
if (current_app_mode_ == AppMode::LOGO) {
|
||||
const char* logo_text = "Modo Logo";
|
||||
int logo_text_width = text_renderer_debug_.getTextWidth(logo_text);
|
||||
int logo_x = (current_screen_width_ - logo_text_width) / 2;
|
||||
text_renderer_debug_.print(logo_x, fixed_y, logo_text, 255, 128, 0); // Naranja
|
||||
int logo_text_width = text_renderer_debug_.getTextWidthPhysical(logo_text);
|
||||
int logo_x = (physical_window_width_ - logo_text_width) / 2;
|
||||
text_renderer_debug_.printAbsolute(logo_x, fixed_y, logo_text, {255, 128, 0, 255}); // Naranja
|
||||
} else if (current_app_mode_ == AppMode::DEMO) {
|
||||
const char* demo_text = "Modo Demo";
|
||||
int demo_text_width = text_renderer_debug_.getTextWidth(demo_text);
|
||||
int demo_x = (current_screen_width_ - demo_text_width) / 2;
|
||||
text_renderer_debug_.print(demo_x, fixed_y, demo_text, 255, 165, 0); // Naranja
|
||||
int demo_text_width = text_renderer_debug_.getTextWidthPhysical(demo_text);
|
||||
int demo_x = (physical_window_width_ - demo_text_width) / 2;
|
||||
text_renderer_debug_.printAbsolute(demo_x, fixed_y, demo_text, {255, 165, 0, 255}); // Naranja
|
||||
} else if (current_app_mode_ == AppMode::DEMO_LITE) {
|
||||
const char* lite_text = "Modo Demo Lite";
|
||||
int lite_text_width = text_renderer_debug_.getTextWidth(lite_text);
|
||||
int lite_x = (current_screen_width_ - lite_text_width) / 2;
|
||||
text_renderer_debug_.print(lite_x, fixed_y, lite_text, 255, 200, 0); // Amarillo-naranja
|
||||
int lite_text_width = text_renderer_debug_.getTextWidthPhysical(lite_text);
|
||||
int lite_x = (physical_window_width_ - lite_text_width) / 2;
|
||||
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_);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
balls_.emplace_back(std::make_unique<Ball>(X, VX, VY, COLOR, texture_, current_screen_width_, current_screen_height_, current_ball_size_, current_gravity_, mass_factor));
|
||||
}
|
||||
setText(); // Actualiza el texto
|
||||
// NOTA: setText() removido - las notificaciones ahora se llaman manualmente desde cada tecla
|
||||
}
|
||||
|
||||
void Engine::setText() {
|
||||
// Suprimir textos durante modos demo
|
||||
if (current_app_mode_ != AppMode::MANUAL) return;
|
||||
if (current_app_mode_ != AppMode::SANDBOX) return;
|
||||
|
||||
// Generar texto de número de pelotas
|
||||
int num_balls = BALL_COUNT_SCENARIOS[scenario_];
|
||||
std::string notification_text;
|
||||
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 {
|
||||
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;
|
||||
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() {
|
||||
for (auto& ball : balls_) {
|
||||
const int SIGNO = ((rand() % 2) * 2) - 1;
|
||||
@@ -1145,6 +1120,9 @@ void Engine::toggleFullscreen() {
|
||||
|
||||
fullscreen_enabled_ = !fullscreen_enabled_;
|
||||
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
|
||||
|
||||
// Actualizar dimensiones físicas después del cambio
|
||||
updatePhysicalWindowSize();
|
||||
}
|
||||
|
||||
void Engine::toggleRealFullscreen() {
|
||||
@@ -1174,6 +1152,9 @@ void Engine::toggleRealFullscreen() {
|
||||
// Actualizar presentación lógica del renderizador
|
||||
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
|
||||
initBalls(scenario_);
|
||||
}
|
||||
@@ -1184,13 +1165,17 @@ void Engine::toggleRealFullscreen() {
|
||||
current_screen_width_ = base_screen_width_;
|
||||
current_screen_height_ = base_screen_height_;
|
||||
|
||||
// Restaurar ventana normal
|
||||
// Restaurar ventana normal con el zoom actual (no hardcoded)
|
||||
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
|
||||
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
|
||||
initBalls(scenario_);
|
||||
}
|
||||
@@ -1376,6 +1361,9 @@ void Engine::setWindowZoom(int new_zoom) {
|
||||
SDL_SetWindowSize(window_, new_width, new_height);
|
||||
SDL_SetWindowPosition(window_, new_x, new_y);
|
||||
current_window_zoom_ = new_zoom;
|
||||
|
||||
// Actualizar tamaño físico de ventana y fuentes
|
||||
updatePhysicalWindowSize();
|
||||
}
|
||||
|
||||
void Engine::zoomIn() {
|
||||
@@ -1386,6 +1374,72 @@ void Engine::zoomOut() {
|
||||
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)
|
||||
// ============================================================================
|
||||
@@ -1436,7 +1490,7 @@ void Engine::setState(AppMode new_mode) {
|
||||
|
||||
void Engine::updateDemoMode() {
|
||||
// Verificar si algún modo demo está activo (DEMO, DEMO_LITE o LOGO)
|
||||
if (current_app_mode_ == AppMode::MANUAL) return;
|
||||
if (current_app_mode_ == AppMode::SANDBOX) return;
|
||||
|
||||
// Actualizar timer
|
||||
demo_timer_ += delta_time_;
|
||||
@@ -1574,9 +1628,10 @@ void Engine::updateDemoMode() {
|
||||
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)
|
||||
if (previous_app_mode_ != AppMode::MANUAL && rand() % 100 < 60) {
|
||||
if (!logo_entered_manually_ && rand() % 100 < 60) {
|
||||
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
|
||||
}
|
||||
}
|
||||
@@ -1775,7 +1830,7 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
// Cambiar sprite (2%)
|
||||
accumulated_weight += DEMO_WEIGHT_SPRITE;
|
||||
if (random_value < accumulated_weight) {
|
||||
switchTexture();
|
||||
switchTexture(false); // Suprimir notificación en modo automático
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1783,6 +1838,15 @@ void Engine::performDemoAction(bool is_lite) {
|
||||
|
||||
// Randomizar todo al iniciar modo DEMO
|
||||
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) {
|
||||
// DEMO LITE: Solo randomizar física/figura + gravedad
|
||||
// Elegir aleatoriamente entre modo física o figura
|
||||
@@ -1818,7 +1882,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
|
||||
|
||||
// 3. Sprite
|
||||
if (rand() % 2 == 0) {
|
||||
switchTexture();
|
||||
switchTexture(false); // Suprimir notificación al activar modo DEMO
|
||||
}
|
||||
|
||||
// 4. Física o Figura
|
||||
@@ -1884,18 +1948,18 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
logo_previous_texture_index_ = current_texture_index_;
|
||||
logo_previous_shape_scale_ = shape_scale_factor_;
|
||||
|
||||
// Buscar índice de textura "tiny"
|
||||
size_t tiny_index = current_texture_index_; // Por defecto mantener actual
|
||||
// Buscar índice de textura "small"
|
||||
size_t small_index = current_texture_index_; // Por defecto mantener actual
|
||||
for (size_t i = 0; i < texture_names_.size(); i++) {
|
||||
if (texture_names_[i] == "tiny") {
|
||||
tiny_index = i;
|
||||
if (texture_names_[i] == "small") {
|
||||
small_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Aplicar configuración fija del Modo Logo
|
||||
if (tiny_index != current_texture_index_) {
|
||||
current_texture_index_ = tiny_index;
|
||||
if (small_index != current_texture_index_) {
|
||||
current_texture_index_ = small_index;
|
||||
int old_size = current_ball_size_;
|
||||
current_ball_size_ = textures_[current_texture_index_]->getWidth();
|
||||
updateBallSizes(old_size, current_ball_size_);
|
||||
@@ -1907,8 +1971,10 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cambiar a tema MONOCHROME
|
||||
theme_manager_->switchToTheme(5); // MONOCHROME
|
||||
// Cambiar a tema aleatorio entre: MONOCHROME, LAVENDER, CRIMSON, ESMERALDA
|
||||
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%
|
||||
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
|
||||
@@ -1932,6 +1998,9 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
logo_target_flip_percentage_ = 0.0f;
|
||||
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)
|
||||
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) {
|
||||
// Salida manual (tecla K): volver a MANUAL
|
||||
setState(AppMode::MANUAL);
|
||||
setState(AppMode::SANDBOX);
|
||||
} else {
|
||||
// Volver al modo previo (DEMO o DEMO_LITE)
|
||||
setState(previous_app_mode_);
|
||||
|
||||
// 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;
|
||||
|
||||
// Guardar tamaño antiguo
|
||||
@@ -2053,16 +2133,11 @@ void Engine::switchTexture() {
|
||||
// Ajustar posiciones según el cambio de tamaño
|
||||
updateBallSizes(old_size, new_size);
|
||||
|
||||
// Mostrar texto informativo (solo si NO estamos en modo demo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
// Obtener nombre de textura (uppercase)
|
||||
// Mostrar notificación con el nombre de la textura (solo si se solicita)
|
||||
if (show_notification) {
|
||||
std::string texture_name = texture_names_[current_texture_index_];
|
||||
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
|
||||
|
||||
text_ = "Sprite: " + texture_name;
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
show_text_ = true;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
showNotificationForAction("Sprite: " + texture_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2104,7 +2179,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
}
|
||||
|
||||
// Mostrar texto informativo (solo si NO estamos en modo demo o logo)
|
||||
if (current_app_mode_ == AppMode::MANUAL) {
|
||||
if (current_app_mode_ == AppMode::SANDBOX) {
|
||||
text_ = "Modo Física";
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
@@ -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)
|
||||
if (active_shape_ && current_app_mode_ == AppMode::MANUAL) {
|
||||
if (active_shape_ && current_app_mode_ == AppMode::SANDBOX) {
|
||||
text_ = std::string("Modo ") + active_shape_->getName();
|
||||
text_pos_ = (current_screen_width_ - text_renderer_.getTextWidth(text_.c_str())) / 2;
|
||||
text_init_time_ = SDL_GetTicks();
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
|
||||
#include "text/textrenderer.h" // for TextRenderer
|
||||
#include "theme_manager.h" // for ThemeManager
|
||||
#include "ui/notifier.h" // for Notifier
|
||||
|
||||
// Modos de aplicación mutuamente excluyentes
|
||||
enum class AppMode {
|
||||
MANUAL, // Control manual del usuario
|
||||
SANDBOX, // Control manual del usuario (modo sandbox)
|
||||
DEMO, // Modo demo completo (auto-play)
|
||||
DEMO_LITE, // Modo demo lite (solo física/figuras)
|
||||
LOGO // Modo logo (easter egg)
|
||||
@@ -54,9 +55,11 @@ class Engine {
|
||||
|
||||
// UI y debug
|
||||
bool show_debug_ = false;
|
||||
bool show_text_ = true;
|
||||
TextRenderer text_renderer_; // Sistema de renderizado de texto para display (centrado)
|
||||
TextRenderer text_renderer_debug_; // Sistema de renderizado de texto para debug (HUD)
|
||||
bool show_text_ = true; // OBSOLETO: usar notifier_ en su lugar
|
||||
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_notifier_; // Sistema de renderizado de texto para notificaciones (tamaño fijo)
|
||||
Notifier notifier_; // Sistema de notificaciones estilo iOS/Android
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
|
||||
@@ -83,6 +86,10 @@ class Engine {
|
||||
int current_screen_width_ = DEFAULT_SCREEN_WIDTH;
|
||||
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)
|
||||
std::unique_ptr<ThemeManager> theme_manager_;
|
||||
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
|
||||
|
||||
// Sistema de Modo DEMO (auto-play)
|
||||
AppMode current_app_mode_ = AppMode::MANUAL; // Modo actual (mutuamente excluyente)
|
||||
AppMode previous_app_mode_ = AppMode::MANUAL; // Modo previo antes de entrar a LOGO
|
||||
AppMode current_app_mode_ = AppMode::SANDBOX; // Modo actual (mutuamente excluyente)
|
||||
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_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)
|
||||
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)
|
||||
int logo_previous_theme_ = 0; // Índice de tema (0-9)
|
||||
size_t logo_previous_texture_index_ = 0;
|
||||
@@ -124,7 +134,7 @@ class Engine {
|
||||
|
||||
// Configuración del sistema de texto (constantes configurables)
|
||||
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
|
||||
|
||||
// Métodos principales del loop
|
||||
@@ -135,7 +145,8 @@ class Engine {
|
||||
|
||||
// Métodos auxiliares
|
||||
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 switchBallsGravity();
|
||||
void enableBallsGravityIfDisabled();
|
||||
@@ -163,14 +174,15 @@ class Engine {
|
||||
void exitLogoMode(bool return_to_demo = false); // Salir del modo logo
|
||||
|
||||
// Sistema de cambio de sprites dinámico
|
||||
void switchTexture(); // Cambia a siguiente textura disponible
|
||||
void updateBallSizes(int old_size, int new_size); // Ajusta posiciones al cambiar tamaño
|
||||
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
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int calculateMaxWindowZoom() const;
|
||||
void setWindowZoom(int new_zoom);
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
void updatePhysicalWindowSize(); // Actualizar tamaño físico real de ventana
|
||||
|
||||
// Rendering
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!isInitialized() || text == nullptr) {
|
||||
return 0;
|
||||
@@ -103,6 +233,23 @@ int TextRenderer::getTextWidth(const char* text) {
|
||||
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() {
|
||||
if (!isInitialized()) {
|
||||
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 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);
|
||||
|
||||
// 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
|
||||
int getTextHeight();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ void ThemeManager::initialize() {
|
||||
"Sunset",
|
||||
"Atardecer",
|
||||
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
|
||||
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: púrpura oscuro
|
||||
std::vector<Color>{
|
||||
@@ -33,6 +34,7 @@ void ThemeManager::initialize() {
|
||||
"Ocean",
|
||||
"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
|
||||
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior: azul marino
|
||||
std::vector<Color>{
|
||||
@@ -46,6 +48,7 @@ void ThemeManager::initialize() {
|
||||
"Neon",
|
||||
"Neón",
|
||||
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
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||
std::vector<Color>{
|
||||
@@ -59,6 +62,7 @@ void ThemeManager::initialize() {
|
||||
"Forest",
|
||||
"Bosque",
|
||||
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
|
||||
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior: marrón tierra
|
||||
std::vector<Color>{
|
||||
@@ -72,6 +76,7 @@ void ThemeManager::initialize() {
|
||||
"RGB",
|
||||
"RGB",
|
||||
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 inferior: blanco puro (sin degradado)
|
||||
std::vector<Color>{
|
||||
@@ -107,6 +112,7 @@ void ThemeManager::initialize() {
|
||||
"Monochrome",
|
||||
"Monocromo",
|
||||
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
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro
|
||||
std::vector<Color>{
|
||||
@@ -120,6 +126,7 @@ void ThemeManager::initialize() {
|
||||
"Lavender",
|
||||
"Lavanda",
|
||||
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
|
||||
25.0f / 255.0f, 30.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior: azul medianoche
|
||||
std::vector<Color>{
|
||||
@@ -133,6 +140,7 @@ void ThemeManager::initialize() {
|
||||
"Crimson",
|
||||
"Carmesí",
|
||||
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
|
||||
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
||||
std::vector<Color>{
|
||||
@@ -146,6 +154,7 @@ void ThemeManager::initialize() {
|
||||
"Emerald",
|
||||
"Esmeralda",
|
||||
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, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior: negro puro
|
||||
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
|
||||
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>{
|
||||
{100, 100, 150}, {120, 120, 170}, {90, 90, 140}, {110, 110, 160},
|
||||
{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
|
||||
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>{
|
||||
{255, 180, 100}, {255, 160, 80}, {255, 200, 120}, {255, 150, 90},
|
||||
{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, 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>{
|
||||
{255, 255, 200}, {255, 255, 180}, {255, 255, 220}, {255, 255, 190},
|
||||
{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
|
||||
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>{
|
||||
{60, 100, 180}, {50, 90, 170}, {70, 110, 190}, {55, 95, 175},
|
||||
{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
|
||||
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>{
|
||||
{100, 220, 255}, {90, 210, 245}, {110, 230, 255}, {95, 215, 250},
|
||||
{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 inferior: negro
|
||||
30, 30, 30, // Color fondo notificación: gris muy oscuro (apagado)
|
||||
std::vector<Color>{
|
||||
{40, 40, 40}, {50, 50, 50}, {45, 45, 45}, {48, 48, 48},
|
||||
{42, 42, 42}, {47, 47, 47}, {44, 44, 44}, {46, 46, 46}
|
||||
@@ -250,6 +265,7 @@ void ThemeManager::initialize() {
|
||||
{
|
||||
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
|
||||
60, 0, 80, // Color fondo notificación: púrpura oscuro (neón encendido)
|
||||
std::vector<Color>{
|
||||
{0, 255, 255}, {255, 0, 255}, {0, 255, 200}, {255, 50, 255},
|
||||
{50, 255, 255}, {255, 0, 200}, {0, 255, 230}, {255, 80, 255}
|
||||
@@ -271,6 +287,7 @@ void ThemeManager::initialize() {
|
||||
{
|
||||
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
|
||||
70, 20, 10, // Color fondo notificación: rojo muy oscuro (brasas)
|
||||
std::vector<Color>{
|
||||
{120, 40, 20}, {140, 35, 15}, {130, 38, 18}, {125, 42, 22},
|
||||
{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
|
||||
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>{
|
||||
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
||||
{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
|
||||
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>{
|
||||
{255, 220, 100}, {255, 200, 80}, {255, 240, 120}, {255, 210, 90},
|
||||
{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
|
||||
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>{
|
||||
{255, 140, 0}, {255, 120, 10}, {255, 160, 20}, {255, 130, 5},
|
||||
{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
|
||||
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>{
|
||||
{100, 255, 180}, {80, 240, 160}, {120, 255, 200}, {90, 245, 170},
|
||||
{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
|
||||
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>{
|
||||
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
||||
{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
|
||||
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>{
|
||||
{100, 220, 255}, {80, 200, 240}, {120, 240, 255}, {90, 210, 245},
|
||||
{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
|
||||
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>{
|
||||
{200, 100, 255}, {180, 80, 240}, {220, 120, 255}, {190, 90, 245},
|
||||
{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
|
||||
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>{
|
||||
{80, 80, 90}, {75, 75, 85}, {85, 85, 95}, {78, 78, 88},
|
||||
{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
|
||||
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>{
|
||||
{255, 80, 40}, {255, 100, 50}, {255, 70, 35}, {255, 90, 45},
|
||||
{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
|
||||
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>{
|
||||
{255, 180, 80}, {255, 200, 100}, {255, 170, 70}, {255, 190, 90},
|
||||
{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
|
||||
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>{
|
||||
{150, 120, 100}, {140, 110, 90}, {160, 130, 110}, {145, 115, 95},
|
||||
{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_));
|
||||
}
|
||||
|
||||
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 {
|
||||
// Obtener color inicial del tema activo (progress = 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(
|
||||
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
|
||||
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
|
||||
snapshot->name_es = themes_[current_theme_index_]->getNameES();
|
||||
|
||||
@@ -65,6 +65,7 @@ class ThemeManager {
|
||||
const char* getCurrentThemeNameEN() const;
|
||||
const char* getCurrentThemeNameES() 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)
|
||||
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);
|
||||
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_;
|
||||
b = text_b_;
|
||||
}
|
||||
void getNotificationBackgroundColor(int& r, int& g, int& b) const override;
|
||||
|
||||
// ========================================
|
||||
// CORE: OBTENER COLORES (interpolados)
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
|
||||
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_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||
std::vector<Color> ball_colors)
|
||||
: name_en_(name_en),
|
||||
name_es_(name_es),
|
||||
text_r_(text_r), text_g_(text_g), text_b_(text_b),
|
||||
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_bottom_r_(bg_bottom_r), bg_bottom_g_(bg_bottom_g), bg_bottom_b_(bg_bottom_b),
|
||||
ball_colors_(std::move(ball_colors)) {
|
||||
|
||||
@@ -23,12 +23,14 @@ class StaticTheme : public Theme {
|
||||
* @param name_en: Nombre en inglés
|
||||
* @param name_es: Nombre en español
|
||||
* @param text_r, text_g, text_b: Color de texto UI
|
||||
* @param 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_bottom_r, bg_bottom_g, bg_bottom_b: Color inferior de fondo
|
||||
* @param ball_colors: Paleta de colores para pelotas
|
||||
*/
|
||||
StaticTheme(const char* name_en, const char* name_es,
|
||||
int text_r, int text_g, int text_b,
|
||||
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_bottom_r, float bg_bottom_g, float bg_bottom_b,
|
||||
std::vector<Color> ball_colors);
|
||||
@@ -46,6 +48,11 @@ class StaticTheme : public Theme {
|
||||
g = text_g_;
|
||||
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
|
||||
@@ -66,6 +73,7 @@ class StaticTheme : public Theme {
|
||||
std::string name_en_;
|
||||
std::string name_es_;
|
||||
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_bottom_r_, bg_bottom_g_, bg_bottom_b_;
|
||||
std::vector<Color> ball_colors_;
|
||||
|
||||
@@ -26,6 +26,7 @@ class Theme {
|
||||
virtual const char* getNameEN() const = 0;
|
||||
virtual const char* getNameES() 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)
|
||||
|
||||
@@ -31,6 +31,9 @@ struct ThemeSnapshot {
|
||||
// Color de texto UI
|
||||
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)
|
||||
std::string name_en;
|
||||
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