#include "core/rendering/overlay.hpp" #include #include #include #include #include "core/rendering/text.hpp" #include "game/options.hpp" namespace Overlay { static std::unique_ptr font_; // --- Aspecte de la notificació --- static constexpr Uint32 NOTIF_BG_COLOR = 0xFF2E1A1A; // Fons blau fosc (ABGR) static constexpr Uint32 NOTIF_TEXT_COLOR = 0xFFFFFF00; // Text cyan (ABGR) static constexpr int NOTIF_PADDING_H = 8; // Padding horitzontal (esquerra/dreta dins la caixa) static constexpr int NOTIF_PADDING_V = 4; // Padding vertical (dalt/baix dins la caixa) static constexpr int NOTIF_MARGIN_X = 4; // Offset des de la vora esquerra de la pantalla static constexpr int NOTIF_MARGIN_Y = 4; // Offset des de la vora superior de la pantalla // --- Animació --- static constexpr float SLIDE_SPEED = 4.0F; // Velocitat de l'animació (unitats/segon) // --- Pantalla --- static constexpr int SCREEN_W = 320; static constexpr int SCREEN_H = 200; // --- Estat de les notificacions --- enum class Status { RISING, STAY, VANISHING, FINISHED }; struct Notification { std::string message; Status status{Status::RISING}; float anim{0.0F}; // 0 = fora de pantalla, 1 = posició final float timer{0.0F}; float duration{2.0F}; int box_w{0}; // Ample de la caixa (calculat al crear) int box_h{0}; // Alçada de la caixa (calculat al crear) }; static std::vector notifications_; static Uint32 last_ticks_ = 0; // --- Render info --- static std::string render_info_text_; // --- Doble ESC per a eixir --- static bool esc_waiting_ = false; void init() { font_ = std::make_unique("fonts/8bithud.fnt", "fonts/8bithud.gif"); last_ticks_ = SDL_GetTicks(); } void destroy() { font_.reset(); notifications_.clear(); } // Pinta un rectangle sòlid dins els límits de la pantalla static void drawRect(Uint32* pixel_data, int rx, int ry, int rw, int rh, Uint32 color) { for (int row = ry; row < ry + rh; row++) { if (row < 0 || row >= SCREEN_H) continue; for (int col = rx; col < rx + rw; col++) { if (col < 0 || col >= SCREEN_W) continue; pixel_data[col + row * SCREEN_W] = color; } } } void render(Uint32* pixel_data) { if (!font_ || !pixel_data) return; // Calcula delta time Uint32 now = SDL_GetTicks(); float dt = static_cast(now - last_ticks_) / 1000.0F; last_ticks_ = now; // Actualitza i pinta cada notificació for (auto& notif : notifications_) { switch (notif.status) { case Status::RISING: notif.anim += SLIDE_SPEED * dt; if (notif.anim >= 1.0F) { notif.anim = 1.0F; notif.status = Status::STAY; notif.timer = 0.0F; } break; case Status::STAY: notif.timer += dt; if (notif.timer >= notif.duration) { notif.status = Status::VANISHING; } break; case Status::VANISHING: notif.anim -= SLIDE_SPEED * dt; if (notif.anim <= 0.0F) { notif.status = Status::FINISHED; } break; case Status::FINISHED: break; } if (notif.status == Status::FINISHED) continue; // Posició: entra des de l'esquerra int target_x = NOTIF_MARGIN_X; int target_y = NOTIF_MARGIN_Y; int box_x = target_x - static_cast((1.0F - notif.anim) * (notif.box_w + NOTIF_MARGIN_X)); int box_y = target_y; // Pinta fons de la caixa drawRect(pixel_data, box_x, box_y, notif.box_w, notif.box_h, NOTIF_BG_COLOR); // Pinta text dins la caixa font_->draw(pixel_data, box_x + NOTIF_PADDING_H, box_y + NOTIF_PADDING_V, notif.message.c_str(), NOTIF_TEXT_COLOR); } // Render info (FPS, driver, shader) — centrat, posició configurable if (Options::render_info.position != Options::RenderInfoPosition::OFF && !render_info_text_.empty()) { int info_w = font_->width(render_info_text_.c_str()); int info_x = (SCREEN_W - info_w) / 2; int info_y = (Options::render_info.position == Options::RenderInfoPosition::TOP) ? 1 : SCREEN_H - font_->charHeight() - 1; // Ombra (1px desplaçat) font_->draw(pixel_data, info_x + 1, info_y + 1, render_info_text_.c_str(), Options::render_info.shadow_color); // Text font_->draw(pixel_data, info_x, info_y, render_info_text_.c_str(), Options::render_info.text_color); } // Elimina les acabades notifications_.erase( std::remove_if(notifications_.begin(), notifications_.end(), [](const Notification& n) { return n.status == Status::FINISHED; }), notifications_.end()); // Si la notificació d'ESC ha desaparegut, reseteja l'estat if (esc_waiting_ && notifications_.empty()) { esc_waiting_ = false; } } void showNotification(const char* text, float duration_seconds) { // Reemplaça la notificació anterior notifications_.clear(); Notification notif; notif.message = text; notif.duration = duration_seconds; notif.box_w = font_->width(text) + NOTIF_PADDING_H * 2; notif.box_h = font_->charHeight() + NOTIF_PADDING_V * 2; notifications_.push_back(notif); } void toggleRenderInfo() { // Cicla: OFF → TOP → BOTTOM → OFF switch (Options::render_info.position) { case Options::RenderInfoPosition::OFF: Options::render_info.position = Options::RenderInfoPosition::TOP; break; case Options::RenderInfoPosition::TOP: Options::render_info.position = Options::RenderInfoPosition::BOTTOM; break; case Options::RenderInfoPosition::BOTTOM: Options::render_info.position = Options::RenderInfoPosition::OFF; break; } } void setRenderInfoText(const char* text) { render_info_text_ = text; } auto isEscConsumed() -> bool { return esc_waiting_; } auto handleEscape() -> bool { if (!esc_waiting_) { // Primera pulsació: mostra avís i consumeix esc_waiting_ = true; showNotification("TORNA A PULSAR ESC PER EIXIR", 2.0F); return true; // Consumit } // Segona pulsació: deixa passar esc_waiting_ = false; return false; } } // namespace Overlay