Files

178 lines
6.3 KiB
C++
Raw Permalink 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/defaults.hpp"
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
#include "core/utils/easing.hpp"
namespace System {
namespace {
// Alias d'àmbit local per accedir compactament als defaults del notifier.
namespace Cfg = Defaults::Notifier;
// 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};
}
} // 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, Cfg::TEXT_SCALE, Cfg::TEXT_SPACING);
const float TEXT_H = Graphics::VectorText::getTextHeight(Cfg::TEXT_SCALE);
box_w_ = TEXT_W + (Cfg::PADDING_H * 2.0F);
box_h_ = TEXT_H + (Cfg::PADDING_V * 2.0F);
text_x_ = (Cfg::CANVAS_WIDTH - TEXT_W) * 0.5F;
y_on_ = Cfg::MARGIN_TOP;
y_off_ = -(box_h_ + Cfg::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_ = Cfg::TEXT_SCALE;
}
void Notifier::notifyInfo(const std::string& text) { notify(text, Cfg::COLOR_INFO, Cfg::DURATION_INFO); }
void Notifier::notifyWarn(const std::string& text) { notify(text, Cfg::COLOR_WARN, Cfg::DURATION_WARN); }
void Notifier::notifyExit(const std::string& text) {
notify(text, Cfg::COLOR_EXIT, Cfg::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_ >= Cfg::SLIDE_DURATION_S) {
y_current_ = y_on_;
status_ = Status::HOLDING;
slide_elapsed_s_ = 0.0F;
} else {
const float T = slide_elapsed_s_ / Cfg::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_ >= Cfg::SLIDE_DURATION_S) {
y_current_ = y_off_;
status_ = Status::HIDDEN;
} else {
const float T = slide_elapsed_s_ / Cfg::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 = (Cfg::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, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // top
gpu->pushLine(X1, Y2, X2, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // bottom
gpu->pushLine(X1, Y1, X1, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // left
gpu->pushLine(X2, Y1, X2, Y2, Cfg::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 + Cfg::PADDING_V;
text_.render(current_text_,
Vec2{.x = text_x_, .y = TEXT_Y},
text_scale_,
Cfg::TEXT_SPACING,
Cfg::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