Files
vibe3_physics/source/ui/notifier.cpp
Sergio Valor a9d7b66e83 Refactorizar estilo del proyecto: .h → .hpp, #pragma once, includes desde raíz
Modernizar convenciones de código C++ aplicando las siguientes directivas:

## Cambios principales

**1. Renombrar headers (.h → .hpp)**
- 36 archivos renombrados a extensión .hpp (estándar C++)
- Mantenidos como .h: stb_image.h, stb_image_resize2.h (librerías C externas)

**2. Modernizar include guards (#ifndef → #pragma once)**
- resource_manager.hpp: #ifndef RESOURCE_MANAGER_H → #pragma once
- resource_pack.hpp: #ifndef RESOURCE_PACK_H → #pragma once
- spatial_grid.hpp: #ifndef SPATIAL_GRID_H → #pragma once

**3. Sistema de includes desde raíz del proyecto**
- CMakeLists.txt: añadido include_directories(${CMAKE_SOURCE_DIR}/source)
- Eliminadas rutas relativas (../) en todos los includes
- Includes ahora usan rutas absolutas desde source/

**Antes:**
```cpp
#include "../defines.h"
#include "../text/textrenderer.h"
```

**Ahora:**
```cpp
#include "defines.hpp"
#include "text/textrenderer.hpp"
```

## Archivos afectados

- 1 archivo CMakeLists.txt modificado
- 36 archivos renombrados (.h → .hpp)
- 32 archivos .cpp actualizados (includes)
- 36 archivos .hpp actualizados (includes + guards)
- 1 archivo tools/ actualizado

**Total: 70 archivos modificados**

## Verificación

 Proyecto compila sin errores
 Todas las rutas de includes correctas
 Include guards modernizados
 Librerías externas C mantienen extensión .h

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 13:49:58 +02:00

276 lines
10 KiB
C++

#include "notifier.hpp"
#include "text/textrenderer.hpp"
#include "theme_manager.hpp"
#include "defines.hpp"
#include "utils/easing_functions.hpp"
#include <SDL3/SDL.h>
// ============================================================================
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
// ============================================================================
// SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay presentación
// lógica activa. Para obtener coordenadas FÍSICAS, necesitamos deshabilitar
// temporalmente la presentación lógica.
static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
// Guardar estado actual de presentación lógica
int logical_w = 0, logical_h = 0;
SDL_RendererLogicalPresentation presentation_mode;
SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &presentation_mode);
// Deshabilitar presentación lógica temporalmente
SDL_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED);
// Obtener viewport en coordenadas físicas (píxeles reales)
SDL_Rect physical_viewport;
SDL_GetRenderViewport(renderer, &physical_viewport);
// Restaurar presentación lógica
SDL_SetRenderLogicalPresentation(renderer, logical_w, logical_h, presentation_mode);
return physical_viewport;
}
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 FÍSICO (píxeles reales, no lógicos)
// CRÍTICO: En F3, SDL_GetRenderViewport() devuelve coordenadas LÓGICAS,
// pero printAbsolute() trabaja en píxeles FÍSICOS. Usar helper para obtener
// viewport en coordenadas físicas.
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
// Centrar en el viewport físico (coordenadas relativas al viewport)
int x = (physical_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);
}