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:
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);
|
||||
}
|
||||
Reference in New Issue
Block a user