#include "window_message.h" #include #include #include "param.h" #include "screen.h" #include "text.h" WindowMessage::WindowMessage( std::shared_ptr text_renderer, const std::string& title, const Config& config) : text_renderer_(std::move(text_renderer)), config_(config), title_(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_); 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); 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() { // Actualizar animación de redimensionado if (resize_animation_.active) { // Aquí necesitarías el delta_time del game loop // Por ahora usamos un valor fijo, pero idealmente se pasaría como parámetro float delta_time = 1.0f / 60.0f; // Asumiendo 60 FPS updateAnimation(delta_time); } } void WindowMessage::show() { ensureTextFits(); visible_ = true; } void WindowMessage::hide() { visible_ = false; } 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; anchor_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 (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() const -> float { return static_cast(param.game.width); } auto WindowMessage::getScreenHeight() const -> float { return static_cast(param.game.height); } void WindowMessage::triggerAutoResize() { if (auto_resize_enabled_) { autoSize(); } } void WindowMessage::updateAnimation(float delta_time) { if (!resize_animation_.active) { return; } resize_animation_.elapsed += delta_time; if (resize_animation_.isFinished()) { // 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()); 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::easeOut(float t) const -> 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); }