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>
276 lines
10 KiB
C++
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);
|
|
}
|