diff --git a/source/core/rendering/notifier.cpp b/source/core/rendering/notifier.cpp index 33e3f4d..3f775a5 100644 --- a/source/core/rendering/notifier.cpp +++ b/source/core/rendering/notifier.cpp @@ -1,11 +1,59 @@ #include "core/rendering/notifier.hpp" +#include +#include #include #include "core/rendering/screen.hpp" #include "core/rendering/text.hpp" #include "utils/defines.hpp" +namespace { + + // Parteix `text` en una llista de línies que caben cadascuna dins + // `max_width`. Algoritme de projecte_2026: tall per espais, la paraula + // que no cap passa a la línia següent. Les paraules aïllades més + // llargues que `max_width` es queden a la seva pròpia línia sense + // trencar-les (cas improbable amb els pretty_names de gamepad retallats + // a ~25 caràcters). + auto wrapToWidth(const std::string& text, int max_width, const Text& font, int kerning) -> std::vector { + std::vector lines; + if (text.empty()) { + lines.emplace_back(); + return lines; + } + + std::string current; + std::size_t i = 0; + while (i < text.size()) { + const std::size_t SPACE_POS = text.find(' ', i); + const bool LAST = (SPACE_POS == std::string::npos); + const std::string WORD = text.substr(i, LAST ? std::string::npos : SPACE_POS - i); + + if (current.empty()) { + current = WORD; + } else { + std::string candidate = current; + candidate += ' '; + candidate += WORD; + if (font.length(candidate, kerning) <= max_width) { + current = std::move(candidate); + } else { + lines.push_back(current); + current = WORD; + } + } + + if (LAST) { break; } + i = SPACE_POS + 1; + } + + lines.push_back(current); + return lines; + } + +} // namespace + auto Notifier::get() -> Notifier& { static Notifier instance_; return instance_; @@ -34,8 +82,8 @@ void Notifier::update(int delta_ms) { break; case Status::VANISHING: y_ -= SLIDE_SPEED * DT; - if (y_ <= static_cast(-HEIGHT)) { - y_ = static_cast(-HEIGHT - MARGIN_TOP); + if (y_ <= static_cast(-current_box_h_)) { + y_ = static_cast(-current_box_h_ - MARGIN_TOP); status_ = Status::HIDDEN; } break; @@ -45,31 +93,45 @@ void Notifier::update(int delta_ms) { } void Notifier::render() { - if (status_ == Status::HIDDEN || text_ == nullptr || current_text_.empty()) { return; } + if (status_ == Status::HIDDEN || text_ == nullptr || current_lines_.empty()) { return; } - const int TEXT_W = text_->length(current_text_, 1); - const int BOX_W = TEXT_W + (2 * PADDING_H); - const int BOX_X = (GameCanvas::WIDTH - BOX_W) / 2; + const int BOX_X = (GameCanvas::WIDTH - current_box_w_) / 2; const int BOX_Y = static_cast(y_); // Fons sòlid amb la paleta UI que ens ha injectat el caller a init() // (ARGB directe al canvas, sense tocar cap surface paletada). - Screen::get().fillRect(BOX_X, BOX_Y, BOX_W, HEIGHT, ui_palette_[current_bg_color_]); + Screen::get().fillRect(BOX_X, BOX_Y, current_box_w_, current_box_h_, ui_palette_[current_bg_color_]); - text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, - GameCanvas::CENTER_X, - BOX_Y + PADDING_V, - current_text_, - 1, - current_text_color_); + const int LINE_STEP = Rendering::Hud::FONT_SIZE_PX + LINE_GAP; + int line_y = BOX_Y + PADDING_V; + for (const auto& line : current_lines_) { + text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, + GameCanvas::CENTER_X, + line_y, + line, + 1, + current_text_color_); + line_y += LINE_STEP; + } } void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms) { // Política simple: la nova notificació reemplaça la que hi havia. Per // gestionar-ne una cua, canviar ací per una FIFO quan calga. - current_text_ = text; + if (text_ == nullptr) { return; } + + current_lines_ = wrapToWidth(text, MAX_TEXT_WIDTH, *text_, 1); + + // Amplada = max de les línies + padding. Alçada = N*glif + (N-1)*gap + padding. + const int MAX_LINE_W = std::accumulate(current_lines_.begin(), current_lines_.end(), 0, [this](int acc, const std::string& s) { return std::max(acc, text_->length(s, 1)); }); + current_box_w_ = MAX_LINE_W + (2 * PADDING_H); + + const int N = static_cast(current_lines_.size()); + current_box_h_ = (N * Rendering::Hud::FONT_SIZE_PX) + ((N - 1) * LINE_GAP) + (2 * PADDING_V); + current_text_color_ = text_color; current_bg_color_ = bg_color; remaining_ms_ = time_ms; + y_ = static_cast(-current_box_h_ - MARGIN_TOP); status_ = Status::RISING; } diff --git a/source/core/rendering/notifier.hpp b/source/core/rendering/notifier.hpp index 82b8e0f..9c3acfb 100644 --- a/source/core/rendering/notifier.hpp +++ b/source/core/rendering/notifier.hpp @@ -5,9 +5,11 @@ #include #include #include +#include #include "core/rendering/hud_constants.hpp" #include "core/rendering/surface.hpp" +#include "utils/defines.hpp" class Text; @@ -19,6 +21,10 @@ class Text; // API crua: Notifier::get().notify(text, text_color, bg_color, time_ms). // Per a treballar amb presets pre-definits, vegeu Notification::notify() a // core/rendering/notification_presets.hpp. +// +// Multilinia: si el text passa de l'amplada màxima (`MAX_TEXT_WIDTH`), es +// parteix per espais i la caixa creix en alçada automàticament. Els textos +// curts (cas habitual) ocupen una sola línia i no canvien d'aspecte. class Notifier { public: static auto get() -> Notifier&; @@ -39,9 +45,13 @@ class Notifier { static constexpr int PADDING_H = 6; static constexpr int PADDING_V = 3; - static constexpr int HEIGHT = Rendering::Hud::FONT_SIZE_PX + (2 * PADDING_V); + static constexpr int LINE_GAP = 1; // Separació vertical entre línies multilinia. static constexpr int MARGIN_TOP = 2; // Separació amb la vora superior del canvas. static constexpr float SLIDE_SPEED = 120.0F; + // Amplada màxima (sense comptar padding) que pot ocupar el text abans que + // `wrapToWidth` el parteixi en línies. Deixem marge respecte a la vora del + // canvas perquè la caixa no toqui els laterals. + static constexpr int MAX_TEXT_WIDTH = GameCanvas::WIDTH - 20 - (2 * PADDING_H); private: enum class Status : std::uint8_t { HIDDEN, @@ -55,8 +65,10 @@ class Notifier { std::shared_ptr text_; Palette ui_palette_{}; Status status_{Status::HIDDEN}; - float y_{static_cast(-HEIGHT - MARGIN_TOP)}; - std::string current_text_; + float y_{0.0F}; + std::vector current_lines_; + int current_box_w_{0}; + int current_box_h_{0}; Uint8 current_text_color_{0}; Uint8 current_bg_color_{0}; int remaining_ms_{0};