feat: Sistema de logo periódico con fade in/out

- Nuevo sistema AppLogo que muestra el logo cada 20 segundos por 5 segundos
- Fade in/out suave de 0.5 segundos con alpha blending
- Máquina de estados: HIDDEN → FADE_IN → VISIBLE → FADE_OUT
- Logo posicionado en cuadrante inferior derecho (1/4 de pantalla)
- Añadido método setAlpha() a Texture para control de transparencia
- Habilitado SDL_BLENDMODE_BLEND en todas las texturas
- Filtrado LINEAR para suavizado del logo escalado
- Desactivado automáticamente en modo SANDBOX

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-18 01:31:29 +02:00
parent de23327861
commit f73a133756
8 changed files with 293 additions and 1 deletions

185
source/app_logo.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "app_logo.h"
#include <SDL3/SDL_render.h> // for SDL_SCALEMODE_LINEAR
#include "external/sprite.h" // for Sprite
#include "external/texture.h" // for Texture
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// Cargar textura del logo desde data/logo/logo.png
std::string resources_dir = getResourcesDirectory();
std::string logo_path = resources_dir + "/data/logo/logo.png";
logo_texture_ = std::make_shared<Texture>(renderer, logo_path);
if (logo_texture_->getWidth() == 0 || logo_texture_->getHeight() == 0) {
// Error al cargar textura
return false;
}
// Configurar filtrado LINEAR para suavizado (mejor para logos escalados)
logo_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
// Crear sprite con la textura
logo_sprite_ = std::make_unique<Sprite>(logo_texture_);
// IMPORTANTE: Configurar el clip para que use toda la textura
float logo_width = static_cast<float>(logo_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight());
logo_sprite_->setClip({0.0f, 0.0f, logo_width, logo_height});
// Calcular factor de escala para que el logo ocupe 1/4 de la pantalla (un cuadrante)
// El logo debe caber en width/2 x height/2
float quadrant_width = screen_width_ / 2.0f;
float quadrant_height = screen_height_ / 2.0f;
float scale_x = quadrant_width / logo_width;
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Aplicar escala
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
logo_sprite_->setSize(scaled_width, scaled_height);
// Posicionar logo en el centro del cuadrante inferior derecho
updateLogoPosition();
return true;
}
void AppLogo::update(float delta_time, AppMode current_mode) {
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
if (current_mode == AppMode::SANDBOX) {
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
current_alpha_ = 0;
return;
}
// Máquina de estados con fade in/out
timer_ += delta_time;
switch (state_) {
case AppLogoState::HIDDEN:
// Esperando el intervalo de espera
if (timer_ >= APPLOGO_DISPLAY_INTERVAL) {
state_ = AppLogoState::FADE_IN;
timer_ = 0.0f;
current_alpha_ = 0;
}
break;
case AppLogoState::FADE_IN:
// Fade in: alpha de 0 a 255
{
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) {
// Fade in completado
state_ = AppLogoState::VISIBLE;
timer_ = 0.0f;
current_alpha_ = 255;
} else {
// Interpolar alpha linealmente
current_alpha_ = static_cast<int>(fade_progress * 255.0f);
}
}
break;
case AppLogoState::VISIBLE:
// Logo completamente visible, esperando duración
if (timer_ >= APPLOGO_DISPLAY_DURATION) {
state_ = AppLogoState::FADE_OUT;
timer_ = 0.0f;
current_alpha_ = 255;
}
break;
case AppLogoState::FADE_OUT:
// Fade out: alpha de 255 a 0
{
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
if (fade_progress >= 1.0f) {
// Fade out completado, volver a HIDDEN
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
current_alpha_ = 0;
} else {
// Interpolar alpha linealmente (inverso)
current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f);
}
}
break;
}
// Aplicar alpha a la textura
if (logo_texture_) {
logo_texture_->setAlpha(current_alpha_);
}
}
void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN && logo_sprite_) {
logo_sprite_->render();
}
}
void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// Recalcular escala y posición del logo
if (logo_sprite_ && logo_texture_) {
float logo_width = static_cast<float>(logo_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight());
// Calcular factor de escala para que el logo ocupe 1/4 de la pantalla
float quadrant_width = screen_width_ / 2.0f;
float quadrant_height = screen_height_ / 2.0f;
float scale_x = quadrant_width / logo_width;
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
// Aplicar escala
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
logo_sprite_->setSize(scaled_width, scaled_height);
// Posicionar logo
updateLogoPosition();
}
}
void AppLogo::updateLogoPosition() {
if (!logo_sprite_ || !logo_texture_) return;
// Calcular tamaño escalado del logo (ya configurado en setSize)
float logo_width = static_cast<float>(logo_texture_->getWidth());
float logo_height = static_cast<float>(logo_texture_->getHeight());
float quadrant_width = screen_width_ / 2.0f;
float quadrant_height = screen_height_ / 2.0f;
float scale_x = quadrant_width / logo_width;
float scale_y = quadrant_height / logo_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
float scaled_width = logo_width * scale;
float scaled_height = logo_height * scale;
// Centro del cuadrante inferior derecho
// Cuadrante inferior derecho va de (width/2, height/2) a (width, height)
// Su centro está en (3/4 * width, 3/4 * height)
float quadrant_center_x = screen_width_ * 0.75f;
float quadrant_center_y = screen_height_ * 0.75f;
// Centrar el logo en ese punto (sprite se posiciona por esquina superior izquierda)
float pos_x = quadrant_center_x - (scaled_width / 2.0f);
float pos_y = quadrant_center_y - (scaled_height / 2.0f);
logo_sprite_->setPos({pos_x, pos_y});
}

50
source/app_logo.h Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
#include <SDL3/SDL_render.h> // for SDL_Renderer
#include <memory> // for unique_ptr, shared_ptr
#include "defines.h" // for AppMode
class Texture;
class Sprite;
// Estados de la máquina de estados del logo
enum class AppLogoState {
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
FADE_IN, // Apareciendo (alpha 0 → 255)
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
FADE_OUT // Desapareciendo (alpha 255 → 0)
};
class AppLogo {
public:
AppLogo() = default;
~AppLogo() = default;
// Inicializar textura y sprite del logo
bool initialize(SDL_Renderer* renderer, int screen_width, int screen_height);
// Actualizar temporizadores y estado de visibilidad
void update(float delta_time, AppMode current_mode);
// Renderizar logo si está visible
void render();
// Actualizar tamaño de pantalla (reposicionar logo)
void updateScreenSize(int screen_width, int screen_height);
private:
std::shared_ptr<Texture> logo_texture_; // Textura del logo
std::unique_ptr<Sprite> logo_sprite_; // Sprite para renderizar
AppLogoState state_ = AppLogoState::HIDDEN; // Estado actual de la máquina de estados
float timer_ = 0.0f; // Contador de tiempo para estado actual
int current_alpha_ = 0; // Alpha actual (0-255)
int screen_width_ = 0; // Ancho de pantalla (para centrar)
int screen_height_ = 0; // Alto de pantalla (para centrar)
// Métodos privados auxiliares
void updateLogoPosition(); // Centrar logo en pantalla
};

View File

@@ -288,6 +288,11 @@ constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progres
constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
// Configuración de AppLogo (logo periódico en pantalla)
constexpr float APPLOGO_DISPLAY_INTERVAL = 20.0f; // Intervalo entre apariciones del logo (segundos)
constexpr float APPLOGO_DISPLAY_DURATION = 5.0f; // Duración de visibilidad del logo (segundos)
constexpr float APPLOGO_FADE_DURATION = 0.5f; // Duración del fade in/out (segundos)
// Configuración de Modo BOIDS (comportamiento de enjambre)
// TIME-BASED CONVERSION (frame-based → time-based):
// - Radios: sin cambios (píxeles)

View File

@@ -254,6 +254,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
boid_manager_ = std::make_unique<BoidManager>();
boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
current_screen_width_, current_screen_height_);
// Inicializar AppLogo (logo periódico en pantalla)
app_logo_ = std::make_unique<AppLogo>();
if (!app_logo_->initialize(renderer_, current_screen_width_, current_screen_height_)) {
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl;
// No es crítico, continuar sin logo
app_logo_.reset();
}
}
return success;
@@ -334,6 +342,11 @@ void Engine::update() {
// Actualizar transiciones de temas (delegado a ThemeManager)
theme_manager_->update(delta_time_);
// Actualizar AppLogo (logo periódico)
if (app_logo_) {
app_logo_->update(delta_time_, state_manager_->getCurrentMode());
}
}
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
@@ -716,6 +729,11 @@ void Engine::render() {
active_shape_.get(), shape_convergence_,
physical_window_width_, physical_window_height_, current_screen_width_);
// Renderizar AppLogo (logo periódico) - después de UI, antes de present
if (app_logo_) {
app_logo_->render();
}
SDL_RenderPresent(renderer_);
}
@@ -797,6 +815,11 @@ void Engine::toggleRealFullscreen() {
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
// Actualizar AppLogo con nueva resolución
if (app_logo_) {
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
}
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
@@ -830,6 +853,11 @@ void Engine::toggleRealFullscreen() {
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
// Actualizar AppLogo con resolución restaurada
if (app_logo_) {
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
}
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla

View File

@@ -10,6 +10,7 @@
#include <string> // for string
#include <vector> // for vector
#include "app_logo.h" // for AppLogo
#include "ball.h" // for Ball
#include "boids_mgr/boid_manager.h" // for BoidManager
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
@@ -105,6 +106,7 @@ class Engine {
std::unique_ptr<BoidManager> boid_manager_; // Gestión de comportamiento boids
std::unique_ptr<StateManager> state_manager_; // Gestión de estados (DEMO/LOGO)
std::unique_ptr<UIManager> ui_manager_; // Gestión de UI (HUD, FPS, notificaciones)
std::unique_ptr<AppLogo> app_logo_; // Gestión de logo periódico en pantalla
// Recursos SDL
SDL_Window* window_ = nullptr;
@@ -160,7 +162,6 @@ class Engine {
// Sistema de Modo DEMO (auto-play) y LOGO
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
AppMode previous_app_mode_ = AppMode::SANDBOX; // Modo previo antes de entrar a LOGO
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)

View File

@@ -128,6 +128,9 @@ bool Texture::loadFromFile(const std::string &file_path) {
// Configurar filtro nearest neighbor para píxel perfect
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
// Habilitar alpha blending para transparencias
SDL_SetTextureBlendMode(new_texture, SDL_BLENDMODE_BLEND);
}
// Destruye la superficie cargada
@@ -169,3 +172,17 @@ int Texture::getHeight() {
void Texture::setColor(int r, int g, int b) {
SDL_SetTextureColorMod(texture_, r, g, b);
}
// Modula el alpha de la textura
void Texture::setAlpha(int alpha) {
if (texture_ != nullptr) {
SDL_SetTextureAlphaMod(texture_, static_cast<Uint8>(alpha));
}
}
// Configurar modo de escalado
void Texture::setScaleMode(SDL_ScaleMode mode) {
if (texture_ != nullptr) {
SDL_SetTextureScaleMode(texture_, mode);
}
}

View File

@@ -44,6 +44,12 @@ class Texture {
// Modula el color de la textura
void setColor(int r, int g, int b);
// Modula el alpha (transparencia) de la textura
void setAlpha(int alpha);
// Configurar modo de escalado (NEAREST para pixel art, LINEAR para suavizado)
void setScaleMode(SDL_ScaleMode mode);
// Getter para batch rendering
SDL_Texture *getSDLTexture() const { return texture_; }
};