Files
orni-attack/source/core/system/notifier.cpp
T

195 lines
7.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// notifier.cpp - Implementació del singleton de notificacions toast
#include "core/system/notifier.hpp"
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
#include "core/utils/easing.hpp"
namespace System {
namespace {
// Geometria del cuadre en coordenades lògiques (1280×720).
constexpr float CANVAS_WIDTH = 1280.0F;
constexpr float MARGIN_TOP = 40.0F;
constexpr float PADDING_H = 16.0F;
constexpr float PADDING_V = 10.0F;
constexpr float BORDER_THICKNESS = 2.0F;
constexpr float TEXT_SCALE = 0.55F;
constexpr float TEXT_SPACING = 2.0F;
constexpr float BORDER_BRIGHTNESS = 1.0F;
// Cinemàtica del slide.
constexpr float SLIDE_DURATION_S = 0.30F;
// Conversió color SDL → float [0,1].
constexpr auto toUnit(Uint8 v) -> float {
return static_cast<float>(v) / 255.0F;
}
// Color del fons: variant fosca del text (0.25× RGB) amb alpha 0.65.
struct UnitRGBA {
float r;
float g;
float b;
float a;
};
constexpr auto textColorFloat(SDL_Color c) -> UnitRGBA {
return UnitRGBA{.r = toUnit(c.r), .g = toUnit(c.g), .b = toUnit(c.b), .a = toUnit(c.a)};
}
constexpr auto bgColorFloat(SDL_Color c) -> UnitRGBA {
constexpr float DARKEN = 0.25F;
constexpr float BG_ALPHA = 0.65F;
return UnitRGBA{
.r = toUnit(c.r) * DARKEN,
.g = toUnit(c.g) * DARKEN,
.b = toUnit(c.b) * DARKEN,
.a = BG_ALPHA};
}
// Presets per als atajos semàntics.
constexpr SDL_Color COLOR_INFO{.r = 80, .g = 230, .b = 255, .a = 255};
constexpr SDL_Color COLOR_WARN{.r = 255, .g = 180, .b = 40, .a = 255};
constexpr SDL_Color COLOR_EXIT{.r = 255, .g = 80, .b = 80, .a = 255};
constexpr float DURATION_INFO = 2.0F;
constexpr float DURATION_WARN = 3.0F;
constexpr float DURATION_EXIT = 3.0F;
} // namespace
std::unique_ptr<Notifier> Notifier::instance;
void Notifier::init(Rendering::Renderer* renderer) {
if (!instance) {
instance = std::unique_ptr<Notifier>(new Notifier(renderer));
}
}
void Notifier::destroy() { instance.reset(); }
auto Notifier::get() -> Notifier* { return instance.get(); }
Notifier::Notifier(Rendering::Renderer* renderer)
: renderer_(renderer),
text_(renderer) {}
void Notifier::notify(const std::string& text, SDL_Color text_color, float duration_s) {
current_text_ = text;
current_color_ = text_color;
hold_remaining_s_ = duration_s;
current_is_exit_ = false;
const float TEXT_W = Graphics::VectorText::getTextWidth(text, TEXT_SCALE, TEXT_SPACING);
const float TEXT_H = Graphics::VectorText::getTextHeight(TEXT_SCALE);
box_w_ = TEXT_W + (PADDING_H * 2.0F);
box_h_ = TEXT_H + (PADDING_V * 2.0F);
text_x_ = (CANVAS_WIDTH - TEXT_W) * 0.5F;
y_on_ = MARGIN_TOP;
y_off_ = -(box_h_ + BORDER_THICKNESS);
// Si ja es veu, reseteja el slide-in des de la posició actual perquè
// la transició sembli continua. Si està amagat, arrenc des de fora.
if (status_ == Status::HIDDEN) {
y_current_ = y_off_;
}
status_ = Status::ENTERING;
slide_elapsed_s_ = 0.0F;
text_scale_ = TEXT_SCALE;
}
void Notifier::notifyInfo(const std::string& text) { notify(text, COLOR_INFO, DURATION_INFO); }
void Notifier::notifyWarn(const std::string& text) { notify(text, COLOR_WARN, DURATION_WARN); }
void Notifier::notifyExit(const std::string& text) {
notify(text, COLOR_EXIT, DURATION_EXIT);
current_is_exit_ = true; // notify() ho ha posat a false; restaurem.
}
void Notifier::update(float delta_time) {
switch (status_) {
case Status::ENTERING: {
slide_elapsed_s_ += delta_time;
if (slide_elapsed_s_ >= SLIDE_DURATION_S) {
y_current_ = y_on_;
status_ = Status::HOLDING;
slide_elapsed_s_ = 0.0F;
} else {
const float T = slide_elapsed_s_ / SLIDE_DURATION_S;
const float K = Utils::Easing::outCubic(T);
y_current_ = y_off_ + ((y_on_ - y_off_) * K);
}
break;
}
case Status::HOLDING: {
hold_remaining_s_ -= delta_time;
if (hold_remaining_s_ <= 0.0F) {
status_ = Status::EXITING;
slide_elapsed_s_ = 0.0F;
}
break;
}
case Status::EXITING: {
slide_elapsed_s_ += delta_time;
if (slide_elapsed_s_ >= SLIDE_DURATION_S) {
y_current_ = y_off_;
status_ = Status::HIDDEN;
} else {
const float T = slide_elapsed_s_ / SLIDE_DURATION_S;
const float K = Utils::Easing::inCubic(T);
y_current_ = y_on_ + ((y_off_ - y_on_) * K);
}
break;
}
case Status::HIDDEN:
default:
break;
}
}
void Notifier::draw() const {
if (status_ == Status::HIDDEN) {
return;
}
const float BOX_X = (CANVAS_WIDTH - box_w_) * 0.5F;
const float BOX_Y = y_current_;
const UnitRGBA TC = textColorFloat(current_color_);
const UnitRGBA BG = bgColorFloat(current_color_);
auto* gpu = renderer_;
// 1. Fons semitransparent.
gpu->pushRect(BOX_X, BOX_Y, box_w_, box_h_, BG.r, BG.g, BG.b, BG.a);
// 2. Bordes (4 línies amb el color del text).
const float X1 = BOX_X;
const float Y1 = BOX_Y;
const float X2 = BOX_X + box_w_;
const float Y2 = BOX_Y + box_h_;
gpu->pushLine(X1, Y1, X2, Y1, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // top
gpu->pushLine(X1, Y2, X2, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // bottom
gpu->pushLine(X1, Y1, X1, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // left
gpu->pushLine(X2, Y1, X2, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // right
// 3. Text centrat dins la caixa, amb color explícit (l'alpha != 0
// li diu al renderShape que no agafe l'oscil·lador global de color).
const float TEXT_Y = BOX_Y + PADDING_V;
text_.render(current_text_,
Vec2{.x = text_x_, .y = TEXT_Y},
text_scale_,
TEXT_SPACING,
BORDER_BRIGHTNESS,
current_color_);
}
auto Notifier::isActiveWindow() const -> bool {
return status_ == Status::ENTERING || status_ == Status::HOLDING;
}
auto Notifier::isExitPromptActive() const -> bool {
return isActiveWindow() && current_is_exit_;
}
} // namespace System