419 lines
13 KiB
C++
419 lines
13 KiB
C++
#include "window_message.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include "param.h"
|
|
#include "screen.h"
|
|
#include "text.h"
|
|
|
|
WindowMessage::WindowMessage(
|
|
std::shared_ptr<Text> 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() {
|
|
// Actualizar animaciones
|
|
if (show_hide_animation_.active || 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() {
|
|
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<std::string>& 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, 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);
|
|
} |