From 5f293cbddfe81e64be7c73073543c6b640e42c0f Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 13 Apr 2026 14:03:45 +0200 Subject: [PATCH] reordenades les layers del overlay consola ara tanca i obri per temps en lloc de velocitat --- source/core/rendering/render_info.cpp | 9 ++-- source/game/ui/console.cpp | 68 +++++++++++---------------- source/game/ui/console.hpp | 18 ++++--- source/game/ui/notifier.cpp | 68 +++++++++++++++++---------- source/game/ui/notifier.hpp | 16 +++---- 5 files changed, 94 insertions(+), 85 deletions(-) diff --git a/source/core/rendering/render_info.cpp b/source/core/rendering/render_info.cpp index 9259479..53520f8 100644 --- a/source/core/rendering/render_info.cpp +++ b/source/core/rendering/render_info.cpp @@ -119,9 +119,10 @@ void RenderInfo::render() const { // Fuente: preferir la de la consola si está disponible auto text_obj = (Console::get() != nullptr) ? Console::get()->getText() : Screen::get()->getText(); - // Posición Y: debajo de la consola + offset animado propio + // Posición Y: debajo de la consola + altura animada de la pila de notificaciones + offset animado propio const int CONSOLE_Y = (Console::get() != nullptr) ? Console::get()->getVisibleHeight() : 0; - const int Y = CONSOLE_Y + static_cast(y_); + const int NOTIFIER_Y = (Notifier::get() != nullptr) ? Notifier::get()->getVisibleHeight() : 0; + const int Y = CONSOLE_Y + NOTIFIER_Y + static_cast(y_); // Rectángulo de fondo: ancho completo, alto ajustado al texto const SDL_FRect RECT = { @@ -141,17 +142,15 @@ void RenderInfo::render() const { MSG_COLOR); } -// Activa o desactiva el overlay y notifica a Notifier del cambio de offset +// Activa o desactiva el overlay (la posición Y se calcula pull-side en render()) void RenderInfo::toggle() { switch (status_) { case Status::HIDDEN: status_ = Status::RISING; Screen::get()->updateZoomFactor(); - if (Notifier::get() != nullptr) { Notifier::get()->addYOffset(HEIGHT); } break; case Status::ACTIVE: status_ = Status::VANISHING; - if (Notifier::get() != nullptr) { Notifier::get()->removeYOffset(HEIGHT); } break; default: break; diff --git a/source/game/ui/console.cpp b/source/game/ui/console.cpp index dd37c49..c264a30 100644 --- a/source/game/ui/console.cpp +++ b/source/game/ui/console.cpp @@ -14,7 +14,7 @@ #include "core/rendering/text.hpp" // Para Text #include "core/resources/resource_cache.hpp" // Para Resource #include "game/options.hpp" // Para Options -#include "game/ui/notifier.hpp" // Para Notifier +#include "utils/easing_functions.hpp" // Para Easing::cubicInOut // ── Helpers de texto ────────────────────────────────────────────────────────── @@ -194,22 +194,16 @@ void Console::update(float delta_time) { // NOLINT(readability-function-cogniti // Animación de altura (resize cuando msg_lines_ cambia); solo en ACTIVE if (status_ == Status::ACTIVE && height_ != target_height_) { - const float PREV_HEIGHT = height_; - if (height_ < target_height_) { - height_ = std::min(height_ + (SLIDE_SPEED * delta_time), target_height_); - } else { - height_ = std::max(height_ - (SLIDE_SPEED * delta_time), target_height_); + if (anim_progress_ == 0.0F) { + // Iniciar animación de resize + anim_start_ = height_; + anim_end_ = target_height_; } - // Actualizar el Notifier incrementalmente con el delta de altura - if (Notifier::get() != nullptr) { - const int DELTA_PX = static_cast(height_) - static_cast(PREV_HEIGHT); - if (DELTA_PX > 0) { - Notifier::get()->addYOffset(DELTA_PX); - notifier_offset_applied_ += DELTA_PX; - } else if (DELTA_PX < 0) { - Notifier::get()->removeYOffset(-DELTA_PX); - notifier_offset_applied_ += DELTA_PX; - } + anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); + height_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); + if (anim_progress_ >= 1.0F) { + height_ = target_height_; + anim_progress_ = 0.0F; } // Reconstruir la Surface al nuevo tamaño (pequeña: 256×~18-72px) const float WIDTH = Options::game.width; @@ -220,28 +214,23 @@ void Console::update(float delta_time) { // NOLINT(readability-function-cogniti // Redibujar texto cada frame redrawText(); - switch (status_) { - case Status::RISING: { - y_ += SLIDE_SPEED * delta_time; - if (y_ >= 0.0F) { - y_ = 0.0F; + // Animación de apertura/cierre (basada en tiempo con easing) + if (status_ == Status::RISING || status_ == Status::VANISHING) { + anim_progress_ = std::min(anim_progress_ + (delta_time / ANIM_DURATION), 1.0F); + y_ = anim_start_ + ((anim_end_ - anim_start_) * Easing::cubicInOut(anim_progress_)); + + if (anim_progress_ >= 1.0F) { + y_ = anim_end_; + anim_progress_ = 0.0F; + if (status_ == Status::RISING) { status_ = Status::ACTIVE; - } - break; - } - case Status::VANISHING: { - y_ -= SLIDE_SPEED * delta_time; - if (y_ <= -height_) { - y_ = -height_; + } else { status_ = Status::HIDDEN; // Resetear el mensaje una vez completamente oculta msg_lines_ = {std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION)}; target_height_ = calcTargetHeight(static_cast(msg_lines_.size())); } - break; } - default: - break; } SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_}; @@ -265,6 +254,9 @@ void Console::toggle() { target_height_ = calcTargetHeight(static_cast(msg_lines_.size())); height_ = target_height_; y_ = -height_; + anim_start_ = y_; + anim_end_ = 0.0F; + anim_progress_ = 0.0F; status_ = Status::RISING; input_line_.clear(); cursor_timer_ = 0.0F; @@ -273,24 +265,18 @@ void Console::toggle() { typewriter_chars_ = static_cast(msg_lines_[0].size()); typewriter_timer_ = 0.0F; SDL_StartTextInput(SDL_GetKeyboardFocus()); - if (Notifier::get() != nullptr) { - const int OFFSET = static_cast(height_); - Notifier::get()->addYOffset(OFFSET); - notifier_offset_applied_ = OFFSET; - } if (on_toggle) { on_toggle(true); } break; case Status::ACTIVE: // Al cerrar: mantener el texto visible hasta que esté completamente oculta + anim_start_ = y_; + anim_end_ = -height_; + anim_progress_ = 0.0F; status_ = Status::VANISHING; - target_height_ = height_; // No animar durante VANISHING + target_height_ = height_; // No animar altura durante VANISHING history_index_ = -1; saved_input_.clear(); SDL_StopTextInput(SDL_GetKeyboardFocus()); - if (Notifier::get() != nullptr) { - Notifier::get()->removeYOffset(notifier_offset_applied_); - notifier_offset_applied_ = 0; - } if (on_toggle) { on_toggle(false); } break; default: diff --git a/source/game/ui/console.hpp b/source/game/ui/console.hpp index 987a938..26ff22b 100644 --- a/source/game/ui/console.hpp +++ b/source/game/ui/console.hpp @@ -51,14 +51,14 @@ class Console { }; // Constantes visuales - static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK - static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN - static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN - static constexpr float SLIDE_SPEED = 180.0F; + static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK + static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN + static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN + static constexpr float ANIM_DURATION = 0.3F; // Duración de cualquier animación (segundos) // Constantes de consola static constexpr std::string_view CONSOLE_NAME = "JDD Console"; - static constexpr std::string_view CONSOLE_VERSION = "v2.2"; + static constexpr std::string_view CONSOLE_VERSION = "v2.3"; static constexpr int MAX_LINE_CHARS = 32; static constexpr int MAX_HISTORY_SIZE = 20; static constexpr float CURSOR_ON_TIME = 0.5F; @@ -99,9 +99,13 @@ class Console { int typewriter_chars_{0}; // Caracteres de msg_lines_ actualmente visibles float typewriter_timer_{0.0F}; + // Animación basada en tiempo (0→1 en ANIM_DURATION) + float anim_progress_{0.0F}; // Progreso normalizado [0, 1] + float anim_start_{0.0F}; // Valor inicial (y_ o height_) + float anim_end_{0.0F}; // Valor final + // Animación de altura dinámica - float target_height_{0.0F}; // Altura objetivo (según número de líneas de mensaje) - int notifier_offset_applied_{0}; // Acumulador del offset enviado al Notifier + float target_height_{0.0F}; // Altura objetivo (según número de líneas de mensaje) // Historial de comandos (navegable con flechas arriba/abajo) std::deque history_; diff --git a/source/game/ui/notifier.cpp b/source/game/ui/notifier.cpp index 561d934..11321d4 100644 --- a/source/game/ui/notifier.cpp +++ b/source/game/ui/notifier.cpp @@ -15,6 +15,7 @@ #include "core/rendering/text.hpp" // Para Text, Text::CENTER_FLAG, Text::COLOR_FLAG #include "core/resources/resource_cache.hpp" // Para Resource #include "game/options.hpp" // Para Options, options, NotificationPosition +#include "game/ui/console.hpp" // Para Console #include "utils/delta_timer.hpp" // Para DeltaTimer #include "utils/utils.hpp" // Para PaletteColor @@ -73,8 +74,11 @@ void Notifier::render() { // Actualiza el estado de las notificaiones void Notifier::update(float delta_time) { + // Base Y leída cada frame: cada notificación se dibuja en rect.y (relativo a BASE) + BASE + const float BASE = static_cast(getStackBaseY()); + for (auto& notification : notifications_) { - // Si la notificación anterior está "saliendo", no hagas nada + // Si la notificación anterior está "entrando", no hagas nada (stall del resto) if (!notifications_.empty() && ¬ification != ¬ifications_.front()) { const auto& previous_notification = *(std::prev(¬ification)); if (previous_notification.state == Status::RISING) { @@ -84,17 +88,17 @@ void Notifier::update(float delta_time) { switch (notification.state) { case Status::RISING: { - const float DISPLACEMENT = SLIDE_SPEED * delta_time; - notification.rect.y += DISPLACEMENT; - - if (notification.rect.y >= notification.y) { - notification.rect.y = notification.y; + const float TARGET = static_cast(notification.y); + notification.rect.y += SLIDE_SPEED * delta_time; + if (notification.rect.y >= TARGET) { + notification.rect.y = TARGET; notification.state = Status::STAY; notification.elapsed_time = 0.0F; } break; } case Status::STAY: { + notification.rect.y = static_cast(notification.y); notification.elapsed_time += delta_time; if (notification.elapsed_time >= notification.display_duration) { notification.state = Status::VANISHING; @@ -103,10 +107,8 @@ void Notifier::update(float delta_time) { } case Status::VANISHING: { - const float DISPLACEMENT = SLIDE_SPEED * delta_time; - notification.rect.y -= DISPLACEMENT; - - const float TARGET_Y = notification.y - notification.travel_dist; + const float TARGET_Y = static_cast(notification.y - notification.travel_dist); + notification.rect.y -= SLIDE_SPEED * delta_time; if (notification.rect.y <= TARGET_Y) { notification.rect.y = TARGET_Y; notification.state = Status::FINISHED; @@ -120,8 +122,13 @@ void Notifier::update(float delta_time) { default: break; } + } - notification.sprite->setPosition(notification.rect); + // Refrescar posiciones de sprite cada frame (convierte rect.y relativo a absoluto) + for (auto& notification : notifications_) { + SDL_FRect sprite_rect = notification.rect; + sprite_rect.y += BASE; + notification.sprite->setPosition(sprite_rect); } clearFinishedNotifications(); @@ -170,15 +177,13 @@ void Notifier::show(std::vector texts, const Style& style, int icon // Posición horizontal float desp_h = ((Options::game.width / 2) - (WIDTH / 2)); - ; - // Posición vertical - const int DESP_V = y_offset_; - - // Offset + // Offset vertical (relativo a la base de la pila, que se consulta cada frame) const auto TRAVEL_DIST = HEIGHT + PADDING_OUT; const int TRAVEL_MOD = 1; - const int OFFSET = !notifications_.empty() ? notifications_.back().y + (TRAVEL_MOD * notifications_.back().travel_dist) : DESP_V; + const int OFFSET = !notifications_.empty() + ? notifications_.back().y + (TRAVEL_MOD * notifications_.back().travel_dist) + : 0; // Crea la notificacion Notification n; @@ -191,8 +196,9 @@ void Notifier::show(std::vector texts, const Style& style, int icon n.texts = texts; n.shape = SHAPE; n.display_duration = style.duration; - const float Y_POS = OFFSET + -TRAVEL_DIST; - n.rect = {.x = desp_h, .y = Y_POS, .w = WIDTH, .h = HEIGHT}; + // Posición inicial relativa a la base: arranca "travel_dist" por encima del target (=OFFSET) + const float Y_POS_REL = static_cast(OFFSET) - TRAVEL_DIST; + n.rect = {.x = desp_h, .y = Y_POS_REL, .w = WIDTH, .h = HEIGHT}; // Crea la textura n.surface = std::make_shared(WIDTH, HEIGHT); @@ -252,8 +258,10 @@ void Notifier::show(std::vector texts, const Style& style, int icon // Deja de dibujar en la textura Screen::get()->setRendererSurface(previuos_renderer); - // Crea el sprite de la notificación - n.sprite = std::make_shared(n.surface, n.rect); + // Crea el sprite de la notificación (rect absoluto a partir del relativo + BASE) + SDL_FRect initial_sprite_rect = n.rect; + initial_sprite_rect.y += static_cast(getStackBaseY()); + n.sprite = std::make_shared(n.surface, initial_sprite_rect); // Añade la notificación a la lista notifications_.emplace_back(n); @@ -278,9 +286,21 @@ void Notifier::clearNotifications() { clearFinishedNotifications(); } -// Ajusta el offset vertical base -void Notifier::addYOffset(int px) { y_offset_ += px; } -void Notifier::removeYOffset(int px) { y_offset_ -= px; } +// Y absoluta de la base de la pila (justo debajo de Console, o 0 si no hay Console) +auto Notifier::getStackBaseY() const -> int { + return Console::get() != nullptr ? Console::get()->getVisibleHeight() : 0; +} + +// Altura animada ocupada por la pila (usa rect.y animado, no el target — para transiciones suaves) +auto Notifier::getVisibleHeight() const -> int { + int bottom = 0; + for (const auto& n : notifications_) { + if (n.state == Status::FINISHED) { continue; } + const int N_BOTTOM = static_cast(n.rect.y + n.rect.h); + if (N_BOTTOM > bottom) { bottom = N_BOTTOM; } + } + return bottom; +} // Obtiene los códigos de las notificaciones auto Notifier::getCodes() -> std::vector { diff --git a/source/game/ui/notifier.hpp b/source/game/ui/notifier.hpp index 967007a..2b2b26b 100644 --- a/source/game/ui/notifier.hpp +++ b/source/game/ui/notifier.hpp @@ -59,9 +59,9 @@ class Notifier { auto isActive() -> bool; // Indica si hay notificaciones activas auto getCodes() -> std::vector; // Obtiene códigos de notificaciones - // Offset vertical (para evitar solapamiento con Console y renderInfo) - void addYOffset(int px); // Suma píxeles al offset base - void removeYOffset(int px); // Resta píxeles al offset base + // Altura animada ocupada por la pila de notificaciones, en píxeles (relativa a la base). + // Crece/decrece suavemente con las animaciones de entrada/salida. + [[nodiscard]] auto getVisibleHeight() const -> int; private: // Tipos anidados @@ -78,8 +78,8 @@ class Notifier { std::vector texts; Status state{Status::RISING}; Shape shape{Shape::SQUARED}; - SDL_FRect rect{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; - int y{0}; + SDL_FRect rect{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; // rect.y es relativo a la base de la pila + int y{0}; // Top objetivo de la notificación relativo a la base de la pila int travel_dist{0}; std::string code; bool can_be_removed{true}; @@ -97,8 +97,9 @@ class Notifier { static Notifier* notifier; // Métodos privados - void clearFinishedNotifications(); // Elimina las notificaciones finalizadas - void clearNotifications(); // Finaliza y elimina todas las notificaciones activas + void clearFinishedNotifications(); // Elimina las notificaciones finalizadas + void clearNotifications(); // Finaliza y elimina todas las notificaciones activas + [[nodiscard]] auto getStackBaseY() const -> int; // Y absoluta de la base de la pila (leída de Console) // Constructor y destructor privados [SINGLETON] Notifier(const std::string& icon_file, const std::string& text); @@ -111,5 +112,4 @@ class Notifier { std::vector notifications_; // Lista de notificaciones activas bool stack_{false}; // Indica si las notificaciones se apilan bool has_icons_{false}; // Indica si el notificador tiene textura para iconos - int y_offset_{0}; // Offset vertical base (ajustado por Console y renderInfo) };