Add: Sistema de notificaciones con colores de fondo temáticos
CARACTERÍSTICAS:
- Notificaciones con fondo personalizado por tema (15 temas)
- Soporte completo para temas estáticos y dinámicos
- Interpolación LERP de colores durante transiciones
- Actualización por frame durante animaciones de temas
IMPLEMENTACIÓN:
Theme System:
- Añadido getNotificationBackgroundColor() a interfaz Theme
- StaticTheme: Color fijo por tema
- DynamicTheme: Interpolación entre keyframes
- ThemeManager: LERP durante transiciones (PHASE 3)
- ThemeSnapshot: Captura color para transiciones suaves
Colores por Tema:
Estáticos (9):
- SUNSET: Púrpura oscuro (120, 40, 80)
- OCEAN: Azul marino (20, 50, 90)
- NEON: Púrpura oscuro (60, 0, 80)
- FOREST: Marrón tierra (70, 50, 30)
- RGB: Gris claro (220, 220, 220)
- MONOCHROME: Gris oscuro (50, 50, 50)
- LAVENDER: Violeta oscuro (80, 50, 100)
- CRIMSON: Rojo oscuro (80, 10, 10)
- EMERALD: Verde oscuro (10, 80, 10)
Dinámicos (6, 20 keyframes totales):
- SUNRISE: 3 keyframes (noche→alba→día)
- OCEAN_WAVES: 2 keyframes (profundo→claro)
- NEON_PULSE: 2 keyframes (apagado→encendido)
- FIRE: 4 keyframes (brasas→llamas→inferno→llamas)
- AURORA: 4 keyframes (verde→violeta→cian→violeta)
- VOLCANIC: 4 keyframes (ceniza→erupción→lava→enfriamiento)
Notifier:
- Añadido SDL_Color bg_color a estructura Notification
- Método show() acepta parámetro bg_color
- renderBackground() usa color dinámico (no negro fijo)
- Soporte para cambios de color cada frame
Engine:
- Obtiene color de fondo desde ThemeManager
- Pasa bg_color al notifier en cada notificación
- Sincronizado con tema activo y transiciones
FIXES:
- TEXT_ABSOLUTE_SIZE cambiado de 16px a 12px (múltiplo nativo)
- Centrado de notificaciones corregido en F3 fullscreen
- updatePhysicalWindowSize() usa SDL_GetCurrentDisplayMode en F3
- Notificaciones centradas correctamente en ventana/F3/F4
🎨 Generated with Claude Code
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -96,6 +96,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;
|
||||
@@ -285,11 +287,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();
|
||||
|
||||
@@ -874,6 +879,10 @@ void Engine::render() {
|
||||
float text_scale_x = static_cast<float>(physical_window_width_) / static_cast<float>(current_screen_width_);
|
||||
float text_scale_y = static_cast<float>(physical_window_height_) / static_cast<float>(current_screen_height_);
|
||||
|
||||
// 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;
|
||||
@@ -898,6 +907,7 @@ void Engine::render() {
|
||||
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_) {
|
||||
@@ -992,6 +1002,9 @@ void Engine::render() {
|
||||
}
|
||||
}
|
||||
|
||||
// Renderizar notificaciones (siempre al final, sobre todo lo demás)
|
||||
notifier_.render();
|
||||
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
@@ -1033,13 +1046,47 @@ void Engine::setText() {
|
||||
// Suprimir textos durante modos demo
|
||||
if (current_app_mode_ != AppMode::MANUAL) 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
|
||||
|
||||
// Obtener color del tema actual para la notificación
|
||||
int text_r, text_g, text_b;
|
||||
theme_manager_->getCurrentThemeTextColor(text_r, text_g, text_b);
|
||||
SDL_Color notification_color = {
|
||||
static_cast<Uint8>(text_r),
|
||||
static_cast<Uint8>(text_g),
|
||||
static_cast<Uint8>(text_b),
|
||||
255
|
||||
};
|
||||
|
||||
// Obtener color de fondo de la notificación desde el tema
|
||||
int bg_r, bg_g, bg_b;
|
||||
theme_manager_->getCurrentNotificationBackgroundColor(bg_r, bg_g, bg_b);
|
||||
SDL_Color notification_bg_color = {
|
||||
static_cast<Uint8>(bg_r),
|
||||
static_cast<Uint8>(bg_g),
|
||||
static_cast<Uint8>(bg_b),
|
||||
255
|
||||
};
|
||||
|
||||
// Mostrar notificación
|
||||
notifier_.show(notification_text, NOTIFICATION_DURATION, notification_color, notification_bg_color);
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -1136,6 +1183,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() {
|
||||
@@ -1388,13 +1438,27 @@ void Engine::zoomOut() {
|
||||
|
||||
void Engine::updatePhysicalWindowSize() {
|
||||
if (real_fullscreen_enabled_) {
|
||||
// En fullscreen real, usar resolución del display
|
||||
// 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 real de la ventana (lógica * zoom)
|
||||
// En modo ventana, obtener tamaño FÍSICO real del framebuffer
|
||||
int window_w = 0, window_h = 0;
|
||||
SDL_GetWindowSize(window_, &window_w, &window_h);
|
||||
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
|
||||
physical_window_width_ = window_w;
|
||||
physical_window_height_ = window_h;
|
||||
}
|
||||
@@ -1407,8 +1471,35 @@ void Engine::updatePhysicalWindowSize() {
|
||||
// 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
|
||||
// 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_, physical_window_width_, physical_window_height_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#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 {
|
||||
@@ -54,9 +55,11 @@ class Engine {
|
||||
|
||||
// UI y debug
|
||||
bool show_debug_ = false;
|
||||
bool show_text_ = true;
|
||||
bool show_text_ = true; // OBSOLETO: usar notifier_ en su lugar
|
||||
TextRenderer text_renderer_; // Sistema de renderizado de texto para display (centrado)
|
||||
TextRenderer text_renderer_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;
|
||||
|
||||
@@ -152,6 +152,68 @@ void TextRenderer::printPhysical(int logical_x, int logical_y, const std::string
|
||||
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);
|
||||
}
|
||||
|
||||
// Preparar rectángulo de destino en coordenadas físicas absolutas
|
||||
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::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;
|
||||
@@ -165,6 +227,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;
|
||||
|
||||
@@ -23,9 +23,18 @@ public:
|
||||
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;
|
||||
|
||||
214
source/ui/notifier.cpp
Normal file
214
source/ui/notifier.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "notifier.h"
|
||||
#include "../text/textrenderer.h"
|
||||
#include "../defines.h"
|
||||
#include "../utils/easing_functions.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
Notifier::Notifier()
|
||||
: renderer_(nullptr)
|
||||
, text_renderer_(nullptr)
|
||||
, window_width_(0)
|
||||
, window_height_(0)
|
||||
, current_notification_(nullptr) {
|
||||
}
|
||||
|
||||
Notifier::~Notifier() {
|
||||
clear();
|
||||
}
|
||||
|
||||
bool Notifier::init(SDL_Renderer* renderer, TextRenderer* text_renderer, int window_width, int window_height) {
|
||||
renderer_ = renderer;
|
||||
text_renderer_ = text_renderer;
|
||||
window_width_ = window_width;
|
||||
window_height_ = window_height;
|
||||
return (renderer_ != nullptr && text_renderer_ != 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, SDL_Color color, SDL_Color bg_color) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Usar duración default si no se especifica
|
||||
if (duration == 0) {
|
||||
duration = NOTIFICATION_DURATION;
|
||||
}
|
||||
|
||||
// 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)
|
||||
notif.color = color;
|
||||
notif.bg_color = bg_color;
|
||||
|
||||
// Añadir a cola
|
||||
notification_queue_.push(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_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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, current_notification_->bg_color);
|
||||
|
||||
// Renderizar texto con alpha usando printAbsolute (tamaño físico fijo)
|
||||
Uint8 text_alpha = static_cast<Uint8>(current_notification_->alpha * 255.0f);
|
||||
SDL_Color text_color = current_notification_->color;
|
||||
text_color.a = text_alpha;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Crear rectángulo para el fondo (en coordenadas físicas)
|
||||
SDL_FRect bg_rect;
|
||||
bg_rect.x = static_cast<float>(x);
|
||||
bg_rect.y = static_cast<float>(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)
|
||||
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);
|
||||
}
|
||||
108
source/ui/notifier.h
Normal file
108
source/ui/notifier.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
|
||||
// Forward declaration
|
||||
class TextRenderer;
|
||||
|
||||
/**
|
||||
* @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)
|
||||
SDL_Color color;
|
||||
SDL_Color bg_color; // Color de fondo de la notificación (RGB)
|
||||
};
|
||||
|
||||
Notifier();
|
||||
~Notifier();
|
||||
|
||||
/**
|
||||
* @brief Inicializa el notifier con un TextRenderer
|
||||
* @param renderer SDL renderer para dibujar
|
||||
* @param text_renderer TextRenderer configurado con tamaño absoluto
|
||||
* @return true si inicialización exitosa
|
||||
*/
|
||||
bool init(SDL_Renderer* renderer, TextRenderer* text_renderer, 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)
|
||||
* @param color Color del texto
|
||||
* @param bg_color Color de fondo de la notificación
|
||||
*/
|
||||
void show(const std::string& text, Uint64 duration = 0, SDL_Color color = {255, 255, 255, 255}, SDL_Color bg_color = {0, 0, 0, 255});
|
||||
|
||||
/**
|
||||
* @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_;
|
||||
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