#include "notifier.h" #include "../text/textrenderer.h" #include "../theme_manager.h" #include "../defines.h" #include "../utils/easing_functions.h" #include 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(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(elapsed) / static_cast(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(fade_elapsed) / static_cast(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(text_r), static_cast(text_g), static_cast(text_b), static_cast(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(bg_r), static_cast(bg_g), static_cast(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(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(x + viewport.x); bg_rect.y = static_cast(y + viewport.y); bg_rect.w = static_cast(width); bg_rect.h = static_cast(height); // Color del tema con alpha Uint8 bg_alpha = static_cast(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(next_notif); }