diff --git a/data/ui.yaml b/data/ui.yaml index 64738ae..1854559 100644 --- a/data/ui.yaml +++ b/data/ui.yaml @@ -66,8 +66,10 @@ hud: shake_ms: 300 # durada del shake que acompanya el flash # Geometria del popup superior (Notifier). En píxels del canvas, llevat de -# slide_speed (px/segon). El padding vertical és asimètric a propòsit: -# padding_top > padding_bottom puja una mica el text dins la caixa. +# slide_ms (durada en ms de l'animació d'entrada i d'eixida; constant +# independentment de la mida de la caixa). El padding vertical és asimètric a +# propòsit: padding_top > padding_bottom puja una mica el text dins la caixa. +# Corbes disponibles: linear, outQuad, inQuad, inOutQuad, outCubic, inCubic. notifier: padding_h: 6 padding_top: 4 @@ -75,7 +77,9 @@ notifier: line_gap: 1 margin_top: 3 border_px: 1 - slide_speed: 120 + slide_ms: 400 + slide_in_curve: outCubic + slide_out_curve: inCubic # Pausa graduada de la GameScene: el món no para d'un colp, s'atura amb # easing i torna a arrancar amb una altra corba. Els timings es poden diff --git a/source/core/rendering/notifier.cpp b/source/core/rendering/notifier.cpp index c3fa13b..02bedc1 100644 --- a/source/core/rendering/notifier.cpp +++ b/source/core/rendering/notifier.cpp @@ -69,26 +69,38 @@ void Notifier::init(std::shared_ptr text, const Palette& ui_palette) { void Notifier::update(int delta_ms) { const auto& geo = UiConfig::get().notifier(); - const float DT = static_cast(delta_ms) / 1000.0F; switch (status_) { - case Status::RISING: - y_ += geo.slide_speed * DT; - if (y_ >= static_cast(geo.margin_top + geo.border_px)) { - y_ = static_cast(geo.margin_top + geo.border_px); + case Status::RISING: { + slide_elapsed_ms_ += delta_ms; + if (slide_elapsed_ms_ >= geo.slide_ms) { + y_ = slide_y_on_; status_ = Status::ACTIVE; + } else { + const float T = static_cast(slide_elapsed_ms_) / static_cast(geo.slide_ms); + const float K = geo.slide_in_curve(T); + y_ = slide_y_off_ + ((slide_y_on_ - slide_y_off_) * K); } break; + } case Status::ACTIVE: remaining_ms_ -= delta_ms; - if (remaining_ms_ <= 0) { status_ = Status::VANISHING; } - break; - case Status::VANISHING: - y_ -= geo.slide_speed * DT; - if (y_ <= static_cast(-current_box_h_ - geo.border_px)) { - y_ = static_cast(-current_box_h_ - geo.border_px - geo.margin_top); - status_ = Status::HIDDEN; + if (remaining_ms_ <= 0) { + slide_elapsed_ms_ = 0; + status_ = Status::VANISHING; } break; + case Status::VANISHING: { + slide_elapsed_ms_ += delta_ms; + if (slide_elapsed_ms_ >= geo.slide_ms) { + y_ = slide_y_off_; + status_ = Status::HIDDEN; + } else { + const float T = static_cast(slide_elapsed_ms_) / static_cast(geo.slide_ms); + const float K = geo.slide_out_curve(T); + y_ = slide_y_on_ + ((slide_y_off_ - slide_y_on_) * K); + } + break; + } default: break; } @@ -146,6 +158,12 @@ void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color, current_text_color_ = text_color; current_bg_color_ = bg_color; remaining_ms_ = time_ms; - y_ = static_cast(-current_box_h_ - geo.border_px - geo.margin_top); + + // Posicions extremes en y (top-left de la caixa). Off = totalment fora + // del canvas per dalt; On = posició final amb la vora respectada. + slide_y_off_ = static_cast(-current_box_h_ - geo.border_px - geo.margin_top); + slide_y_on_ = static_cast(geo.margin_top + geo.border_px); + slide_elapsed_ms_ = 0; + y_ = slide_y_off_; status_ = Status::RISING; } diff --git a/source/core/rendering/notifier.hpp b/source/core/rendering/notifier.hpp index c1b858f..1e0fdf2 100644 --- a/source/core/rendering/notifier.hpp +++ b/source/core/rendering/notifier.hpp @@ -68,4 +68,11 @@ class Notifier { Uint8 current_text_color_{0}; Uint8 current_bg_color_{0}; int remaining_ms_{0}; + + // Animació d'entrada/eixida per temps. `slide_elapsed_ms_` avança + // amb el delta dins de RISING/VANISHING; els extrems s'avaluen a + // `notify()` perquè depenen de la mida de la caixa. + int slide_elapsed_ms_{0}; + float slide_y_off_{0.0F}; + float slide_y_on_{0.0F}; }; diff --git a/source/core/ui/ui_config.cpp b/source/core/ui/ui_config.cpp index 8c57160..e7ff25a 100644 --- a/source/core/ui/ui_config.cpp +++ b/source/core/ui/ui_config.cpp @@ -110,7 +110,9 @@ UiConfig::UiConfig() .line_gap = Defaults::UI::Notifier::LINE_GAP, .margin_top = Defaults::UI::Notifier::MARGIN_TOP, .border_px = Defaults::UI::Notifier::BORDER_PX, - .slide_speed = Defaults::UI::Notifier::SLIDE_SPEED, + .slide_ms = Defaults::UI::Notifier::SLIDE_MS, + .slide_in_curve = curveByName(Defaults::UI::Notifier::SLIDE_IN_CURVE), + .slide_out_curve = curveByName(Defaults::UI::Notifier::SLIDE_OUT_CURVE), } { } @@ -210,7 +212,9 @@ void UiConfig::load(const std::string& file_path) { readField(n, "line_gap", notifier_.line_gap); readField(n, "margin_top", notifier_.margin_top); readField(n, "border_px", notifier_.border_px); - readField(n, "slide_speed", notifier_.slide_speed); + readField(n, "slide_ms", notifier_.slide_ms); + readCurve(n, "slide_in_curve", notifier_.slide_in_curve); + readCurve(n, "slide_out_curve", notifier_.slide_out_curve); } std::cout << "UiConfig: carregat des de " << file_path << '\n'; diff --git a/source/core/ui/ui_config.hpp b/source/core/ui/ui_config.hpp index a06727c..f8b5508 100644 --- a/source/core/ui/ui_config.hpp +++ b/source/core/ui/ui_config.hpp @@ -20,9 +20,15 @@ class UiConfig { int kerning; }; + // Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1]. + // Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar + // el punter a la corresponent funció de `utils/easing.hpp`. + using CurveFn = auto (*)(float) -> float; + // Geometria del Notifier (popup superior). Carregada des de // data/ui.yaml secció `notifier:`; els valors per defecte vénen de - // Defaults::UI::Notifier. + // Defaults::UI::Notifier. El moviment és per temps (slide_ms) i no per + // velocitat, perquè una caixa més gran no triga més a entrar/eixir. struct Notifier { int padding_h; int padding_top; @@ -30,7 +36,9 @@ class UiConfig { int line_gap; int margin_top; int border_px; - float slide_speed; + int slide_ms; + CurveFn slide_in_curve; + CurveFn slide_out_curve; }; static auto get() -> UiConfig&; @@ -64,11 +72,6 @@ class UiConfig { [[nodiscard]] auto roomText() const -> Uint8 { return room_text_; } [[nodiscard]] auto roomTextOutline() const -> Uint8 { return room_text_outline_; } - // Signatura d'una funció d'easing: t ∈ [0, 1] → valor ∈ [0, 1]. - // Permet triar corbes des de data/ui.yaml per nom (cadena) i guardar - // el punter a la corresponent funció de `utils/easing.hpp`. - using CurveFn = auto (*)(float) -> float; - // Timings (ms). [[nodiscard]] auto notificationMs() const -> int { return notification_ms_; } [[nodiscard]] auto exitMs() const -> int { return exit_ms_; } diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index e045345..1da9411 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -172,7 +172,10 @@ namespace Defaults { // propòsit: PADDING_TOP > PADDING_BOTTOM puja una mica el text // dins la caixa i deixa la base més arrapada al vora inferior. // MARGIN_TOP és la separació entre la vora superior del canvas i - // la línia exterior de la caixa. SLIDE_SPEED en píxels/segon. + // la línia exterior de la caixa. SLIDE_MS és la durada de l'entrada + // i de l'eixida (independent de la mida de la caixa, perquè el + // moviment va per temps i no per velocitat). Les corbes són noms + // del registre d'`Easing` (veure UiConfig::curveByName). namespace Notifier { constexpr int PADDING_H = 6; constexpr int PADDING_TOP = 4; @@ -180,7 +183,9 @@ namespace Defaults { constexpr int LINE_GAP = 1; constexpr int MARGIN_TOP = 3; constexpr int BORDER_PX = 1; - constexpr float SLIDE_SPEED = 120.0F; + constexpr int SLIDE_MS = 400; + constexpr const char* SLIDE_IN_CURVE = "outCubic"; + constexpr const char* SLIDE_OUT_CURVE = "inCubic"; } // namespace Notifier // Preset per a la confirmació d'eixida (ESC doble). La durada ha