#include "notifier.h" #include // Para SDL_RenderFillRect, SDL_FRect, SDL_RenderClear #include // Para remove_if #include // Para basic_string, string #include #include // Para vector #include "audio.h" // Para Audio #include "param.h" // Para Param, param, ParamNotification, ParamGame #include "screen.h" // Para Screen #include "sprite.h" // Para Sprite #include "text.h" // Para Text #include "texture.h" // Para Texture // Singleton Notifier* Notifier::instance = nullptr; // Inicializa la instancia única del singleton void Notifier::init(const std::string& icon_file, std::shared_ptr text) { Notifier::instance = new Notifier(icon_file, text); } // Libera la instancia void Notifier::destroy() { delete Notifier::instance; } // Obtiene la instancia auto Notifier::get() -> Notifier* { return Notifier::instance; } // Constructor Notifier::Notifier(std::string icon_file, std::shared_ptr text) : renderer_(Screen::get()->getRenderer()), icon_texture_(!icon_file.empty() ? std::make_unique(renderer_, icon_file) : nullptr), text_(std::move(text)), bg_color_(param.notification.color), wait_time_(150), stack_(false), has_icons_(!icon_file.empty()) {} // Dibuja las notificaciones por pantalla void Notifier::render() { for (int i = (int)notifications_.size() - 1; i >= 0; --i) { notifications_[i].sprite->render(); } } // Actualiza el estado de las notificaiones void Notifier::update() { for (int i = 0; i < (int)notifications_.size(); ++i) { if (!shouldProcessNotification(i)) { break; } processNotification(i); } clearFinishedNotifications(); } auto Notifier::shouldProcessNotification(int index) const -> bool { // Si la notificación anterior está "saliendo", no hagas nada return index <= 0 || notifications_[index - 1].state != NotificationStatus::RISING; } void Notifier::processNotification(int index) { auto& notification = notifications_[index]; notification.counter++; playNotificationSoundIfNeeded(notification); updateNotificationState(index); notification.sprite->setPosition(notification.rect); } void Notifier::playNotificationSoundIfNeeded(const Notification& notification) { // Hace sonar la notificación en el primer frame if (notification.counter == 1 && param.notification.sound && notification.state == NotificationStatus::RISING) { Audio::get()->playSound("notify.wav", Audio::Group::INTERFACE); } } void Notifier::updateNotificationState(int index) { auto& notification = notifications_[index]; switch (notification.state) { case NotificationStatus::RISING: handleRisingState(index); break; case NotificationStatus::STAY: handleStayState(index); break; case NotificationStatus::VANISHING: handleVanishingState(index); break; default: break; } } void Notifier::handleRisingState(int index) { auto& notification = notifications_[index]; const float STEP = (float)notification.counter / notification.travel_dist; const int ALPHA = 255 * STEP; moveNotificationVertically(notification, param.notification.pos_v == NotifyPosition::TOP ? 1 : -1); notification.texture->setAlpha(ALPHA); if (notification.rect.y == notification.y) { transitionToStayState(index); } } void Notifier::handleStayState(int index) { auto& notification = notifications_[index]; if (notification.counter == wait_time_) { notification.state = NotificationStatus::VANISHING; notification.counter = 0; } } void Notifier::handleVanishingState(int index) { auto& notification = notifications_[index]; const float STEP = notification.counter / (float)notification.travel_dist; const int ALPHA = 255 * (1 - STEP); moveNotificationVertically(notification, param.notification.pos_v == NotifyPosition::TOP ? -1 : 1); notification.texture->setAlpha(ALPHA); if (notification.rect.y == notification.y - notification.travel_dist) { notification.state = NotificationStatus::FINISHED; } } void Notifier::moveNotificationVertically(Notification& notification, int direction) { notification.rect.y += direction; } void Notifier::transitionToStayState(int index) { auto& notification = notifications_[index]; notification.state = NotificationStatus::STAY; notification.texture->setAlpha(255); notification.counter = 0; } // Elimina las notificaciones finalizadas void Notifier::clearFinishedNotifications() { for (int i = (int)notifications_.size() - 1; i >= 0; --i) { if (notifications_[i].state == NotificationStatus::FINISHED) { notifications_.erase(notifications_.begin() + i); } } } void Notifier::show(std::vector texts, int icon, const std::string& code) { // Si no hay texto, acaba if (texts.empty()) { return; } // Si las notificaciones no se apilan, elimina las anteriores if (!stack_) { clearAllNotifications(); } // Elimina las cadenas vacías texts.erase(std::remove_if(texts.begin(), texts.end(), [](const std::string& s) { return s.empty(); }), texts.end()); // Encuentra la cadena más larga std::string longest; for (const auto& text : texts) { if (text.length() > longest.length()) { longest = text; } } // Inicializa variables constexpr int ICON_SIZE = 16; constexpr int PADDING_OUT = 1; const float PADDING_IN_H = text_->getCharacterSize(); const float PADDING_IN_V = text_->getCharacterSize() / 2; const int ICON_SPACE = icon >= 0 ? ICON_SIZE + PADDING_IN_H : 0; const float WIDTH = text_->lenght(longest) + (PADDING_IN_H * 2) + ICON_SPACE; const float HEIGHT = (text_->getCharacterSize() * texts.size()) + (PADDING_IN_V * 2); const auto SHAPE = NotificationShape::SQUARED; // Posición horizontal float desp_h = 0; switch (param.notification.pos_h) { case NotifyPosition::LEFT: desp_h = PADDING_OUT; break; case NotifyPosition::MIDDLE: desp_h = ((param.game.width / 2) - (WIDTH / 2)); break; case NotifyPosition::RIGHT: desp_h = param.game.width - WIDTH - PADDING_OUT; break; default: desp_h = 0; break; } // Posición vertical const int DESP_V = (param.notification.pos_v == NotifyPosition::TOP) ? PADDING_OUT : (param.game.height - HEIGHT - PADDING_OUT); // Offset const auto TRAVEL_DIST = HEIGHT + PADDING_OUT; auto offset = notifications_.empty() ? DESP_V : notifications_.back().y + (param.notification.pos_v == NotifyPosition::TOP ? TRAVEL_DIST : -TRAVEL_DIST); // Crea la notificacion Notification n; // Inicializa variables n.code = code; n.y = offset; n.travel_dist = TRAVEL_DIST; n.texts = texts; n.shape = SHAPE; const float POS_Y = offset + (param.notification.pos_v == NotifyPosition::TOP ? -TRAVEL_DIST : TRAVEL_DIST); n.rect = {desp_h, POS_Y, WIDTH, HEIGHT}; // Crea la textura n.texture = std::make_shared(renderer_); n.texture->createBlank(WIDTH, HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET); n.texture->setBlendMode(SDL_BLENDMODE_BLEND); // Prepara para dibujar en la textura n.texture->setAsRenderTarget(renderer_); // Dibuja el fondo de la notificación SDL_SetRenderDrawColor(renderer_, bg_color_.r, bg_color_.g, bg_color_.b, 255); SDL_FRect rect; if (SHAPE == NotificationShape::ROUNDED) { rect = {4, 0, WIDTH - (4 * 2), HEIGHT}; SDL_RenderFillRect(renderer_, &rect); rect = {4 / 2, 1, WIDTH - 4, HEIGHT - 2}; SDL_RenderFillRect(renderer_, &rect); rect = {1, 4 / 2, WIDTH - 2, HEIGHT - 4}; SDL_RenderFillRect(renderer_, &rect); rect = {0, 4, WIDTH, HEIGHT - (4 * 2)}; SDL_RenderFillRect(renderer_, &rect); } else if (SHAPE == NotificationShape::SQUARED) { SDL_RenderClear(renderer_); } // Dibuja el icono de la notificación if (has_icons_ && icon >= 0 && texts.size() >= 2) { auto sp = std::make_unique(icon_texture_, (SDL_FRect){0, 0, ICON_SIZE, ICON_SIZE}); sp->setPosition({PADDING_IN_H, PADDING_IN_V, ICON_SIZE, ICON_SIZE}); sp->setSpriteClip(SDL_FRect{ static_cast(ICON_SIZE * (icon % 10)), static_cast(ICON_SIZE * (icon / 10)), ICON_SIZE, ICON_SIZE}); sp->render(); } // Escribe el texto de la notificación const Color COLOR{255, 255, 255}; int iterator = 0; for (const auto& text : texts) { text_->writeColored(PADDING_IN_H + ICON_SPACE, PADDING_IN_V + iterator * (text_->getCharacterSize() + 1), text, COLOR); ++iterator; } // Deja de dibujar en la textura SDL_SetRenderTarget(renderer_, nullptr); // Crea el sprite de la notificación n.sprite = std::make_shared(n.texture, n.rect); // Deja la notificación invisible n.texture->setAlpha(0); // Añade la notificación a la lista notifications_.emplace_back(n); } // Finaliza y elimnina todas las notificaciones activas void Notifier::clearAllNotifications() { for (auto& notification : notifications_) { notification.state = NotificationStatus::FINISHED; } clearFinishedNotifications(); } // Obtiene los códigos de las notificaciones auto Notifier::getCodes() -> std::vector { std::vector codes; for (const auto& notification : notifications_) { codes.emplace_back(notification.code); } return codes; }