Files
vibe3_physics/source/ui/notifier.cpp
Sergio Valor 5b674c8ea6 fix: Notifier centrado correcto en viewport (F3 letterbox)
Problema:
- Notificaciones se centraban usando dimensión física de ventana
- En modo letterbox (F3 INTEGER/LETTERBOX) aparecían en barras negras
- Mismo issue que tenía Help Overlay

Causa:
- notifier.cpp:165 usaba `window_width_` para calcular centrado
- En F3 letterbox: viewport visible < ventana física
- Ejemplo: ventana 1920px, viewport 1280px con offset 320px
- Resultado: notificación descentrada fuera del área visible

Solución:
- Obtener viewport con SDL_GetRenderViewport() antes de calcular posición
- Usar `viewport.w` en lugar de `window_width_` para centrado
- Coordenadas relativas al viewport, printAbsolute() aplica offset automáticamente

Código modificado:
- notifier.cpp:162-170 - Centrado usando viewport dimensions

Resultado:
 Notificaciones centradas en área visible (viewport)
 No aparecen en barras negras en F3
 Funciona correctamente en ventana, F3 y F4

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 07:47:10 +02:00

251 lines
9.3 KiB
C++

#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);
// Obtener viewport actual (en modo letterbox F3 tiene dimensiones más pequeñas)
// CRÍTICO: Centrar usando dimensiones del VIEWPORT, no de la ventana física
// printAbsolute() aplicará el offset del viewport automáticamente
SDL_Rect viewport;
SDL_GetRenderViewport(renderer_, &viewport);
// Centrar en el viewport (coordenadas relativas al viewport, no absolutas)
int x = (viewport.w / 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);
}