#include "window_message.hpp" #include #include #include "param.hpp" #include "screen.hpp" #include "text.hpp" WindowMessage::WindowMessage( std::shared_ptr text_renderer, std::string title, const Config& config) : text_renderer_(std::move(text_renderer)), config_(config), title_(std::move(title)), title_style_(Text::CENTER | Text::COLOR, config_.title_color, config_.title_color, 0, -2), text_style_(Text::CENTER | Text::COLOR, config_.text_color, config_.text_color, 0, -2) { } void WindowMessage::render() { if (!visible_) { return; } SDL_Renderer* renderer = Screen::get()->getRenderer(); // Dibujar fondo con transparencia SDL_SetRenderDrawColor(renderer, config_.bg_color.r, config_.bg_color.g, config_.bg_color.b, config_.bg_color.a); SDL_RenderFillRect(renderer, &rect_); // Dibujar borde SDL_SetRenderDrawColor(renderer, config_.border_color.r, config_.border_color.g, config_.border_color.b, config_.border_color.a); SDL_RenderRect(renderer, &rect_); // Solo mostrar contenido si no estamos en animación de show/hide if (shouldShowContent()) { float current_y = rect_.y + config_.padding; float available_width = getAvailableTextWidth(); // Dibujar título si existe if (!title_.empty()) { std::string visible_title = getTruncatedText(title_, available_width); if (!visible_title.empty()) { text_renderer_->writeStyle( rect_.x + (rect_.w / 2.0F), current_y, visible_title, title_style_); } current_y += text_renderer_->getCharacterSize() + config_.title_separator_spacing; // Línea separadora debajo del título (solo si hay título visible) if (!visible_title.empty()) { SDL_SetRenderDrawColor(renderer, config_.border_color.r, config_.border_color.g, config_.border_color.b, config_.border_color.a); SDL_RenderLine(renderer, rect_.x + config_.padding, current_y - (config_.title_separator_spacing / 2.0F), rect_.x + rect_.w - config_.padding, current_y - (config_.title_separator_spacing / 2.0F)); } } // Dibujar textos for (const auto& text : texts_) { std::string visible_text = getTruncatedText(text, available_width); if (!visible_text.empty()) { text_renderer_->writeStyle( rect_.x + (rect_.w / 2.0F), current_y, visible_text, text_style_); } current_y += text_renderer_->getCharacterSize() + config_.line_spacing; } } } void WindowMessage::update(float delta_time) { // Actualizar animaciones if (show_hide_animation_.active || resize_animation_.active) { updateAnimation(delta_time); } } void WindowMessage::show() { if (visible_) { return; // Ya visible } visible_ = true; ensureTextFits(); // Detener cualquier animación anterior resize_animation_.stop(); // Iniciar animación de mostrar desde tamaño 0 show_hide_animation_.startShow(rect_.w, rect_.h); rect_.w = 0.0F; rect_.h = 0.0F; updatePosition(); // Reposicionar con tamaño 0 } void WindowMessage::hide() { if (!visible_) { return; // Ya oculto } // Detener cualquier animación anterior resize_animation_.stop(); // Guardar el tamaño actual para la animación show_hide_animation_.target_width = rect_.w; show_hide_animation_.target_height = rect_.h; // Iniciar animación de ocultar hacia tamaño 0 show_hide_animation_.startHide(); } void WindowMessage::setTitle(const std::string& title) { title_ = title; triggerAutoResize(); } void WindowMessage::setText(const std::string& text) { texts_.clear(); texts_.push_back(text); triggerAutoResize(); } void WindowMessage::setTexts(const std::vector& texts) { texts_ = texts; triggerAutoResize(); } void WindowMessage::addText(const std::string& text) { texts_.push_back(text); triggerAutoResize(); } void WindowMessage::clearTexts() { texts_.clear(); triggerAutoResize(); } void WindowMessage::setPosition(float x, float y, PositionMode mode) { anchor_ = {.x = x, .y = y}; position_mode_ = mode; updatePosition(); } void WindowMessage::setSize(float width, float height) { rect_.w = width; rect_.h = height; updatePosition(); // Reposicionar después de cambiar el tamaño } void WindowMessage::centerOnScreen() { setPosition(getScreenWidth() / 2.0F, getScreenHeight() / 2.0F, PositionMode::CENTERED); } void WindowMessage::autoSize() { if (show_hide_animation_.active) { return; // No redimensionar durante show/hide } if (resize_animation_.active) { resize_animation_.stop(); // Detener animación anterior } float old_width = rect_.w; float old_height = rect_.h; calculateAutoSize(); // Solo animar si hay cambio en el tamaño y la ventana está visible if (visible_ && (old_width != rect_.w || old_height != rect_.h)) { resize_animation_.start(old_width, old_height, rect_.w, rect_.h); // Restaurar el tamaño anterior para que la animación funcione rect_.w = old_width; rect_.h = old_height; } else { updatePosition(); // Reposicionar después de ajustar el tamaño } } void WindowMessage::updateStyles() { title_style_ = Text::Style(Text::CENTER | Text::COLOR, config_.title_color, config_.title_color, 0, -2); text_style_ = Text::Style(Text::CENTER | Text::COLOR, config_.text_color, config_.text_color, 0, -2); } void WindowMessage::updatePosition() { switch (position_mode_) { case PositionMode::CENTERED: rect_.x = anchor_.x - rect_.w / 2.0F; rect_.y = anchor_.y - rect_.h / 2.0F; break; case PositionMode::FIXED: rect_.x = anchor_.x; rect_.y = anchor_.y; break; } // Asegurar que la ventana esté dentro de los límites de la pantalla rect_.x = std::max(0.0F, std::min(rect_.x, getScreenWidth() - rect_.w)); rect_.y = std::max(0.0F, std::min(rect_.y, getScreenHeight() - rect_.h)); } void WindowMessage::ensureTextFits() { float required_width = calculateContentWidth() + (config_.padding * 2) + config_.text_safety_margin; float required_height = calculateContentHeight() + (config_.padding * 2) + config_.text_safety_margin; // Verificar si el tamaño actual es suficiente if (rect_.w < required_width || rect_.h < required_height) { autoSize(); // Recalcular tamaño automáticamente } } void WindowMessage::calculateAutoSize() { float content_width = calculateContentWidth(); float content_height = calculateContentHeight(); // Calcular dimensiones con padding y margen de seguridad rect_.w = content_width + (config_.padding * 2) + config_.text_safety_margin; rect_.h = content_height + (config_.padding * 2); // Aplicar límites mínimos rect_.w = std::max(rect_.w, config_.min_width); rect_.h = std::max(rect_.h, config_.min_height); // Aplicar límites máximos basados en el tamaño de pantalla float max_width = getScreenWidth() * config_.max_width_ratio; float max_height = getScreenHeight() * config_.max_height_ratio; rect_.w = std::min(rect_.w, max_width); rect_.h = std::min(rect_.h, max_height); } auto WindowMessage::calculateContentHeight() const -> float { float height = 0; // Altura del título if (!title_.empty()) { height += text_renderer_->getCharacterSize() + config_.title_separator_spacing; } // Altura de los textos if (!texts_.empty()) { height += (texts_.size() * text_renderer_->getCharacterSize()); if (texts_.size() > 1) { height += ((texts_.size() - 1) * config_.line_spacing); } } return height; } auto WindowMessage::calculateContentWidth() const -> float { float max_width = config_.min_width - (config_.padding * 2); // Ancho mínimo sin padding // Ancho del título if (!title_.empty()) { float title_width = text_renderer_->length(title_, -2); max_width = std::max(max_width, title_width); } // Ancho de los textos for (const auto& text : texts_) { float text_width = text_renderer_->length(text, -2); max_width = std::max(max_width, text_width); } return max_width; } auto WindowMessage::getScreenWidth() -> float { return param.game.width; } auto WindowMessage::getScreenHeight() -> float { return param.game.height; } void WindowMessage::triggerAutoResize() { if (auto_resize_enabled_) { autoSize(); } } void WindowMessage::updateAnimation(float delta_time) { if (show_hide_animation_.active) { updateShowHideAnimation(delta_time); } if (resize_animation_.active) { updateResizeAnimation(delta_time); } } void WindowMessage::updateShowHideAnimation(float delta_time) { if (!show_hide_animation_.active) { return; } show_hide_animation_.elapsed += delta_time; if (show_hide_animation_.isFinished(config_.animation_duration)) { // Animación terminada if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) { // Mostrar completado rect_.w = show_hide_animation_.target_width; rect_.h = show_hide_animation_.target_height; } else if (show_hide_animation_.type == ShowHideAnimation::Type::HIDING) { // Ocultar completado rect_.w = 0.0F; rect_.h = 0.0F; visible_ = false; } show_hide_animation_.stop(); updatePosition(); } else { // Interpolar el tamaño float progress = easeOut(show_hide_animation_.getProgress(config_.animation_duration)); if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) { // Crecer desde 0 hasta el tamaño objetivo rect_.w = show_hide_animation_.target_width * progress; rect_.h = show_hide_animation_.target_height * progress; } else if (show_hide_animation_.type == ShowHideAnimation::Type::HIDING) { // Decrecer desde el tamaño actual hasta 0 rect_.w = show_hide_animation_.target_width * (1.0F - progress); rect_.h = show_hide_animation_.target_height * (1.0F - progress); } updatePosition(); // Mantener la posición centrada durante la animación } } void WindowMessage::updateResizeAnimation(float delta_time) { if (!resize_animation_.active) { return; } resize_animation_.elapsed += delta_time; if (resize_animation_.isFinished(config_.animation_duration)) { // Animación terminada rect_.w = resize_animation_.target_width; rect_.h = resize_animation_.target_height; resize_animation_.stop(); updatePosition(); } else { // Interpolar el tamaño float progress = easeOut(resize_animation_.getProgress(config_.animation_duration)); rect_.w = resize_animation_.start_width + (resize_animation_.target_width - resize_animation_.start_width) * progress; rect_.h = resize_animation_.start_height + (resize_animation_.target_height - resize_animation_.start_height) * progress; updatePosition(); // Mantener la posición centrada durante la animación } } auto WindowMessage::shouldShowContent() const -> bool { // No mostrar contenido durante animaciones de show/hide return !show_hide_animation_.active; } auto WindowMessage::easeOut(float t) -> float { // Función de suavizado ease-out cuadrática return 1.0F - ((1.0F - t) * (1.0F - t)); } auto WindowMessage::getAvailableTextWidth() const -> float { // Ancho disponible = ancho total - padding en ambos lados return rect_.w - (config_.padding * 2.0F); } auto WindowMessage::getTruncatedText(const std::string& text, float available_width) const -> std::string { if (text.empty()) { return text; } // Si el texto completo cabe, devolverlo tal como está int text_width = text_renderer_->length(text, -2); if (text_width <= available_width) { return text; } // Si no hay espacio suficiente, devolver string vacío if (available_width < 10.0F) { // Mínimo espacio para al menos un carácter return ""; } // Buscar cuántos caracteres caben usando búsqueda binaria int left = 0; int right = text.length(); int best_length = 0; while (left <= right) { int mid = (left + right) / 2; std::string partial = text.substr(0, mid); int partial_width = text_renderer_->length(partial, -2); if (partial_width <= available_width) { best_length = mid; left = mid + 1; } else { right = mid - 1; } } return text.substr(0, best_length); }