From 2b35ac018706c68afe3a9ab9059242e16ba8e0c1 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 9 Aug 2025 20:44:56 +0200 Subject: [PATCH] =?UTF-8?q?ServiceMenu:=20afegida=20animaci=C3=B3=20de=20a?= =?UTF-8?q?pertura/tancament?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/ui/menu_renderer.cpp | 332 +++++++++++++++++++++++------------- source/ui/menu_renderer.h | 80 +++++++-- source/ui/service_menu.cpp | 98 ++++++----- source/ui/service_menu.h | 50 ++---- 4 files changed, 351 insertions(+), 209 deletions(-) diff --git a/source/ui/menu_renderer.cpp b/source/ui/menu_renderer.cpp index 6740de9..8e5ab36 100644 --- a/source/ui/menu_renderer.cpp +++ b/source/ui/menu_renderer.cpp @@ -1,21 +1,41 @@ #include "menu_renderer.h" -#include // Para max, min -#include // Para pair, move +#include +#include + +#include "color.h" +#include "menu_option.h" +#include "param.h" +#include "screen.h" +#include "text.h" +#include "utils.h" + +// --- Implementación de las estructuras de animación --- + +void MenuRenderer::ResizeAnimation::start(float from_w, float from_h, float to_w, float to_h) { + start_width = from_w; start_height = from_h; + target_width = to_w; target_height = to_h; + elapsed = 0.0f; active = true; +} +void MenuRenderer::ResizeAnimation::stop() { active = false; elapsed = 0.0f; } + +void MenuRenderer::ShowHideAnimation::startShow(float to_w, float to_h) { + type = Type::SHOWING; target_width = to_w; target_height = to_h; + elapsed = 0.0f; active = true; +} +void MenuRenderer::ShowHideAnimation::startHide() { type = Type::HIDING; elapsed = 0.0f; active = true; } +void MenuRenderer::ShowHideAnimation::stop() { type = Type::NONE; active = false; elapsed = 0.0f; } -#include "color.h" // Para Color, generateMirroredCycle, ColorCycleStyle -#include "menu_option.h" // Para MenuOption -#include "param.h" // Para Param, param, ParamServiceMenu, ParamGame -#include "screen.h" // Para Screen -#include "text.h" // Para Text, Text::CENTER, Text::COLOR -#include "utils.h" // Para Zone, truncateWithEllipsis MenuRenderer::MenuRenderer(const ServiceMenu *menu_state, std::shared_ptr element_text, std::shared_ptr title_text) : element_text_(std::move(element_text)), title_text_(std::move(title_text)) { initializeMaxSizes(); + setPosition(param.game.game_area.center_x, param.game.game_area.center_y, PositionMode::CENTERED); } void MenuRenderer::render(const ServiceMenu *menu_state) { + if (!visible_) return; + // Dibuja la sombra if (param.service_menu.drop_shadow) { SDL_FRect shadow_rect = {rect_.x + 5, rect_.y + 5, rect_.w, rect_.h}; @@ -32,71 +52,107 @@ void MenuRenderer::render(const ServiceMenu *menu_state) { SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255); SDL_RenderRect(Screen::get()->getRenderer(), &rect_); SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_); + + // Solo renderizar contenido si la animación lo permite + if (shouldShowContent()) { + // Dibuja el título + float y = rect_.y + title_padding_; + title_text_->writeDX(Text::COLOR | Text::CENTER, rect_.x + rect_.w / 2.0f, y, menu_state->getTitle(), -4, param.service_menu.title_color); - // Dibuja el título - float y = rect_.y + title_padding_; - title_text_->writeDX(Text::COLOR | Text::CENTER, param.game.game_area.center_x, y, menu_state->getTitle(), -4, param.service_menu.title_color); + // Dibuja la línea separadora + y = rect_.y + upper_height_; + SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255); + SDL_RenderLine(Screen::get()->getRenderer(), rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y); - // Dibuja la línea separadora - y = rect_.y + upper_height_; - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255); - SDL_RenderLine(Screen::get()->getRenderer(), rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y); + // Dibuja las opciones + y = options_y_; + const auto &option_pairs = menu_state->getOptionPairs(); - // Dibuja las opciones - y = options_y_; - const auto &option_pairs = menu_state->getOptionPairs(); + for (size_t i = 0; i < option_pairs.size(); ++i) { + const bool IS_SELECTED = (i == menu_state->getSelectedIndex()); + const Color ¤t_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color; - for (size_t i = 0; i < option_pairs.size(); ++i) { - const bool IS_SELECTED = (i == menu_state->getSelectedIndex()); - const Color ¤t_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color; - - if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) { - // Para opciones alineadas a la izquierda, truncamos el valor si es necesario - const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - - element_text_->length(option_pairs.at(i).first, -2) - - ServiceMenu::MIN_GAP_OPTION_VALUE; - - std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, available_width); - - element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2); - const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->length(truncated_value, -2); - element_text_->writeColored(X, y, truncated_value, current_color, -2); - } else { - // Para opciones centradas, también truncamos si es necesario - const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2); - std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, available_width); - - element_text_->writeDX(Text::CENTER | Text::COLOR, rect_.x + rect_.w / 2, y, truncated_caption, -2, current_color); + if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) { + const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - element_text_->length(option_pairs.at(i).first, -2) - ServiceMenu::MIN_GAP_OPTION_VALUE; + std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, available_width); + element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2); + const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->length(truncated_value, -2); + element_text_->writeColored(X, y, truncated_value, current_color, -2); + } else { + const int available_width = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2); + std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, available_width); + element_text_->writeDX(Text::CENTER | Text::COLOR, rect_.x + rect_.w / 2.0f, y, truncated_caption, -2, current_color); + } + y += options_height_ + options_padding_; } - y += options_height_ + options_padding_; } } void MenuRenderer::update(const ServiceMenu *menu_state) { - if (resizing_) { - updateResizeAnimation(); + float delta_time = 1.0f / 60.0f; // Asumiendo 60 FPS + updateAnimations(delta_time); + + if (visible_) { + updateColorCounter(); + param.service_menu.selected_color = getAnimatedSelectedColor(); } - updateColorCounter(); - param.service_menu.selected_color = getAnimatedSelectedColor(); } +// --- Nuevos métodos de control --- + +void MenuRenderer::show(const ServiceMenu* menu_state) { + if (visible_) return; + visible_ = true; + + // Calcula el tamaño final y lo usa para la animación + SDL_FRect target_rect = calculateNewRect(menu_state); + + // Detener cualquier animación anterior + resize_animation_.stop(); + + // Iniciar animación de mostrar + show_hide_animation_.startShow(target_rect.w, target_rect.h); + + // El tamaño inicial es cero para la animación + rect_.w = 0.0f; + rect_.h = 0.0f; + updatePosition(); +} + +void MenuRenderer::hide() { + if (!visible_ || show_hide_animation_.type == ShowHideAnimation::Type::HIDING) return; + + // Detener animación de resize si la hubiera + resize_animation_.stop(); + + // Guardar tamaño actual para la animación de ocultar + show_hide_animation_.target_width = rect_.w; + show_hide_animation_.target_height = rect_.h; + show_hide_animation_.startHide(); +} + +void MenuRenderer::setPosition(float x, float y, PositionMode mode) { + anchor_x_ = x; + anchor_y_ = y; + position_mode_ = mode; + updatePosition(); +} + +// --- Métodos de layout --- + void MenuRenderer::onLayoutChanged(const ServiceMenu *menu_state) { - // Cuando la lógica del menú notifica un cambio, el renderer recalcula su layout precalculateMenuWidths(menu_state->getAllOptions(), menu_state); setAnchors(menu_state); resize(menu_state); } void MenuRenderer::setLayout(const ServiceMenu *menu_state) { - // Cuando la lógica del menú notifica un cambio, el renderer recalcula su layout precalculateMenuWidths(menu_state->getAllOptions(), menu_state); setAnchors(menu_state); setSize(menu_state); } void MenuRenderer::initializeMaxSizes() { - // Establecemos los límites máximos basados en el tamaño de la pantalla - // Dejamos un margen del 10% en cada lado para que el menú no ocupe toda la pantalla max_menu_width_ = static_cast(param.game.game_area.rect.w * 0.9F); max_menu_height_ = static_cast(param.game.game_area.rect.h * 0.9F); } @@ -104,10 +160,7 @@ void MenuRenderer::initializeMaxSizes() { void MenuRenderer::setAnchors(const ServiceMenu *menu_state) { size_t max_entries = 0; for (int i = 0; i < 5; ++i) { - size_t count = menu_state->countOptionsInGroup(static_cast(i)); - if (count > max_entries) { - max_entries = count; - } + max_entries = std::max(max_entries, menu_state->countOptionsInGroup(static_cast(i))); } options_height_ = element_text_->getCharacterSize(); @@ -124,110 +177,152 @@ void MenuRenderer::setAnchors(const ServiceMenu *menu_state) { auto MenuRenderer::calculateNewRect(const ServiceMenu *menu_state) -> SDL_FRect { width_ = std::min(static_cast(getMenuWidthForGroup(menu_state->getCurrentGroup())), max_menu_width_); - const auto &display_options = menu_state->getDisplayOptions(); lower_height_ = ((!display_options.empty() ? display_options.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); height_ = std::min(upper_height_ + lower_height_, max_menu_height_); - - return {(param.game.width - width_) / 2.0F, (param.game.height - height_) / 2.0F, (float)width_, (float)height_}; + + SDL_FRect new_rect = {0, 0, (float)width_, (float)height_}; + + // La posición x, y se establecerá en `updatePosition` + return new_rect; } + void MenuRenderer::resize(const ServiceMenu *menu_state) { SDL_FRect new_rect = calculateNewRect(menu_state); - if (rect_.x != new_rect.x || rect_.y != new_rect.y || rect_.w != new_rect.w || rect_.h != new_rect.h) { - rect_anim_from_ = rect_; - rect_anim_to_ = new_rect; - resize_anim_step_ = 0; - resizing_ = true; + if (rect_.w != new_rect.w || rect_.h != new_rect.h) { + // En lugar de la animación antigua, usamos la nueva + resize_animation_.start(rect_.w, rect_.h, new_rect.w, new_rect.h); } else { - rect_ = setRect(new_rect); - resizing_ = false; + // Si no hay cambio de tamaño, solo actualizamos la posición + updatePosition(); } - options_y_ = new_rect.y + upper_height_ + lower_padding_; -} - -void MenuRenderer::setSize(const ServiceMenu *menu_state) { - rect_ = setRect(calculateNewRect(menu_state)); - resizing_ = false; options_y_ = rect_.y + upper_height_ + lower_padding_; } -void MenuRenderer::updateResizeAnimation() { - if (!resizing_) { - return; - } - ++resize_anim_step_; - float t = static_cast(resize_anim_step_) / resize_anim_steps_; - if (t >= 1.0F) { - rect_ = setRect(rect_anim_to_); - resizing_ = false; - return; - } - float ease = 1 - (1 - t) * (1 - t); - SDL_FRect rect = - {rect_anim_from_.x + (rect_anim_to_.x - rect_anim_from_.x) * ease, - rect_anim_from_.y + (rect_anim_to_.y - rect_anim_from_.y) * ease, - rect_anim_from_.w + (rect_anim_to_.w - rect_anim_from_.w) * ease, - rect_anim_from_.h + (rect_anim_to_.h - rect_anim_from_.h) * ease}; - rect_ = setRect(rect); +void MenuRenderer::setSize(const ServiceMenu *menu_state) { + SDL_FRect new_rect = calculateNewRect(menu_state); + rect_.w = new_rect.w; + rect_.h = new_rect.h; + + show_hide_animation_.stop(); + resize_animation_.stop(); + + updatePosition(); + options_y_ = rect_.y + upper_height_ + lower_padding_; + border_rect_ = {rect_.x - 1, rect_.y + 1, rect_.w + 2, rect_.h - 2}; } -void MenuRenderer::precalculateMenuWidths(const std::vector> &all_options, const ServiceMenu *menu_state) { - for (int &w : group_menu_widths_) { - w = ServiceMenu::MIN_WIDTH; +// --- Métodos de animación y posición --- + +void MenuRenderer::updateAnimations(float delta_time) { + if (show_hide_animation_.active) { + updateShowHideAnimation(delta_time); } + if (resize_animation_.active) { + updateResizeAnimation(delta_time); + } +} + +void MenuRenderer::updateShowHideAnimation(float delta_time) { + show_hide_animation_.elapsed += delta_time; + float duration = show_hide_animation_.duration; + + if (show_hide_animation_.elapsed >= duration) { + if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) { + rect_.w = show_hide_animation_.target_width; + rect_.h = show_hide_animation_.target_height; + } else if (show_hide_animation_.type == ShowHideAnimation::Type::HIDING) { + rect_.w = 0.0f; rect_.h = 0.0f; visible_ = false; + } + show_hide_animation_.stop(); + updatePosition(); + } else { + float progress = easeOut(show_hide_animation_.elapsed / duration); + if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) { + 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) { + rect_.w = show_hide_animation_.target_width * (1.0f - progress); + rect_.h = show_hide_animation_.target_height * (1.0f - progress); + } + updatePosition(); + } + options_y_ = rect_.y + upper_height_ + lower_padding_; +} + +void MenuRenderer::updateResizeAnimation(float delta_time) { + resize_animation_.elapsed += delta_time; + float duration = resize_animation_.duration; + + if (resize_animation_.elapsed >= duration) { + rect_.w = resize_animation_.target_width; + rect_.h = resize_animation_.target_height; + resize_animation_.stop(); + updatePosition(); + } else { + float progress = easeOut(resize_animation_.elapsed / 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(); + } + options_y_ = rect_.y + upper_height_ + lower_padding_; +} + + +void MenuRenderer::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; + } + // Actualizar el rectángulo del borde junto con el principal + border_rect_ = {rect_.x - 1, rect_.y + 1, rect_.w + 2, rect_.h - 2}; +} + +// Resto de métodos (sin cambios significativos) + +void MenuRenderer::precalculateMenuWidths(const std::vector> &all_options, const ServiceMenu *menu_state) { + for (int &w : group_menu_widths_) w = ServiceMenu::MIN_WIDTH; for (int group = 0; group < 5; ++group) { auto sg = static_cast(group); - int max_option_width = 0; - int max_value_width = 0; + int max_option_width = 0, max_value_width = 0; for (const auto &option : all_options) { - if (option->getGroup() != sg) { - continue; - } + if (option->getGroup() != sg) continue; max_option_width = std::max(max_option_width, element_text_->length(option->getCaption(), -2)); if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) { - // Para calcular el ancho máximo, necesitamos considerar la truncación - int max_available_value_width = static_cast(max_menu_width_) - max_option_width - - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - - ServiceMenu::MIN_GAP_OPTION_VALUE; - - int actual_value_width = getTruncatedValueWidth(option->getValueAsString(), max_available_value_width); - max_value_width = std::max(max_value_width, actual_value_width); + int max_available_value_width = static_cast(max_menu_width_) - max_option_width - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - ServiceMenu::MIN_GAP_OPTION_VALUE; + max_value_width = std::max(max_value_width, getTruncatedValueWidth(option->getValueAsString(), max_available_value_width)); } } size_t total_width = max_option_width + (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2); - if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) { - total_width += ServiceMenu::MIN_GAP_OPTION_VALUE + max_value_width; - } - group_menu_widths_[group] = std::min(std::max((int)ServiceMenu::MIN_WIDTH, (int)total_width), - static_cast(max_menu_width_)); + if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) total_width += ServiceMenu::MIN_GAP_OPTION_VALUE + max_value_width; + group_menu_widths_[group] = std::min(std::max((int)ServiceMenu::MIN_WIDTH, (int)total_width), static_cast(max_menu_width_)); } } -auto MenuRenderer::getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int { - return group_menu_widths_[static_cast(group)]; -} - +auto MenuRenderer::getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int { return group_menu_widths_[static_cast(group)]; } void MenuRenderer::updateColorCounter() { static Uint64 last_update_ = SDL_GetTicks(); - Uint64 current_ticks = SDL_GetTicks(); - if (current_ticks - last_update_ >= 50) { + if (SDL_GetTicks() - last_update_ >= 50) { color_counter_++; - last_update_ = current_ticks; + last_update_ = SDL_GetTicks(); } } - auto MenuRenderer::getAnimatedSelectedColor() const -> Color { static auto color_cycle_ = generateMirroredCycle(param.service_menu.selected_color, ColorCycleStyle::HUE_WAVE); return color_cycle_.at(color_counter_ % color_cycle_.size()); } - auto MenuRenderer::setRect(SDL_FRect rect) -> SDL_FRect { border_rect_ = {rect.x - 1, rect.y + 1, rect.w + 2, rect.h - 2}; return rect; } - auto MenuRenderer::getTruncatedValueWidth(const std::string &value, int available_width) const -> int { int value_width = element_text_->length(value, -2); if (value_width <= available_width) { @@ -278,4 +373,7 @@ auto MenuRenderer::getTruncatedValue(const std::string &value, int available_wid } return truncated; -} \ No newline at end of file +} + +auto MenuRenderer::easeOut(float t) const -> float { return 1.0f - (1.0f - t) * (1.0f - t); } +auto MenuRenderer::shouldShowContent() const -> bool { return !show_hide_animation_.active; } \ No newline at end of file diff --git a/source/ui/menu_renderer.h b/source/ui/menu_renderer.h index d7008f1..375ec83 100644 --- a/source/ui/menu_renderer.h +++ b/source/ui/menu_renderer.h @@ -1,28 +1,43 @@ #pragma once -#include // Para SDL_FRect, Uint32 +#include -#include // Para array -#include // Para size_t -#include // Para shared_ptr, unique_ptr -#include // Para string -#include // Para vector +#include +#include +#include +#include +#include -#include "color.h" // Para Color -#include "ui/service_menu.h" // Para ServiceMenu +#include "color.h" +#include "ui/service_menu.h" class MenuOption; -// Forward declarations class Text; class MenuRenderer { public: + // --- Nuevo: Enum para el modo de posicionamiento --- + enum class PositionMode { + CENTERED, // La ventana se centra en el punto especificado + FIXED // La esquina superior izquierda coincide con el punto + }; + MenuRenderer(const ServiceMenu *menu_state, std::shared_ptr element_text, std::shared_ptr title_text); - // Métodos principales de la vista + // --- Métodos principales de la vista --- void render(const ServiceMenu *menu_state); void update(const ServiceMenu *menu_state); + // --- Nuevos: Métodos de control de visibilidad y animación --- + void show(const ServiceMenu *menu_state); + void hide(); + [[nodiscard]] auto isVisible() const -> bool { return visible_; } + [[nodiscard]] auto isFullyVisible() const -> bool { return visible_ && !show_hide_animation_.active && !resize_animation_.active; } + [[nodiscard]] auto isAnimating() const -> bool { return resize_animation_.active || show_hide_animation_.active; } + + // --- Nuevos: Métodos de configuración de posición --- + void setPosition(float x, float y, PositionMode mode); + // Método para notificar al renderer que el layout puede haber cambiado void onLayoutChanged(const ServiceMenu *menu_state); void setLayout(const ServiceMenu *menu_state); @@ -49,17 +64,41 @@ class MenuRenderer { size_t lower_height_ = 0; size_t lower_padding_ = 0; Uint32 color_counter_ = 0; + bool visible_ = false; + // --- Posicionamiento --- + PositionMode position_mode_ = PositionMode::CENTERED; + float anchor_x_ = 0.0f; + float anchor_y_ = 0.0f; + // --- Límites de tamaño máximo --- size_t max_menu_width_ = 0; size_t max_menu_height_ = 0; - // --- Variables para animación de resize --- - SDL_FRect rect_anim_from_{}; - SDL_FRect rect_anim_to_{}; - int resize_anim_step_ = 0; - int resize_anim_steps_ = 8; - bool resizing_ = false; + // --- Estructuras de Animación (inspirado en WindowMessage) --- + struct ResizeAnimation { + bool active = false; + float start_width, start_height; + float target_width, target_height; + float elapsed = 0.0f; + float duration = 0.2f; + + void start(float from_w, float from_h, float to_w, float to_h); + void stop(); + } resize_animation_; + + struct ShowHideAnimation { + enum class Type { NONE, SHOWING, HIDING }; + Type type = Type::NONE; + bool active = false; + float target_width, target_height; + float elapsed = 0.0f; + float duration = 0.25f; + + void startShow(float to_w, float to_h); + void startHide(); + void stop(); + } show_hide_animation_; // --- Anchos precalculados --- std::array group_menu_widths_ = {}; @@ -70,7 +109,12 @@ class MenuRenderer { auto calculateNewRect(const ServiceMenu *menu_state) -> SDL_FRect; void resize(const ServiceMenu *menu_state); void setSize(const ServiceMenu *menu_state); - void updateResizeAnimation(); + + void updateAnimations(float delta_time); + void updateResizeAnimation(float delta_time); + void updateShowHideAnimation(float delta_time); + void updatePosition(); + void precalculateMenuWidths(const std::vector> &all_options, const ServiceMenu *menu_state); [[nodiscard]] auto getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int; [[nodiscard]] auto getAnimatedSelectedColor() const -> Color; @@ -78,4 +122,6 @@ class MenuRenderer { auto setRect(SDL_FRect rect) -> SDL_FRect; [[nodiscard]] auto getTruncatedValueWidth(const std::string &value, int available_width) const -> int; [[nodiscard]] auto getTruncatedValue(const std::string &value, int available_width) const -> std::string; + [[nodiscard]] auto easeOut(float t) const -> float; + [[nodiscard]] auto shouldShowContent() const -> bool; }; \ No newline at end of file diff --git a/source/ui/service_menu.cpp b/source/ui/service_menu.cpp index 5592f84..e764726 100644 --- a/source/ui/service_menu.cpp +++ b/source/ui/service_menu.cpp @@ -31,7 +31,9 @@ ServiceMenu::ServiceMenu() auto element_text = Resource::get()->getText("04b_25_flat"); auto title_text = Resource::get()->getText("04b_25_flat_2x"); + // El renderer ahora se inicializa con su configuración renderer_ = std::make_unique(this, element_text, title_text); + restart_message_ui_ = std::make_unique(element_text, Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), param.service_menu.title_color); define_buttons_ = std::make_unique(); @@ -39,44 +41,53 @@ ServiceMenu::ServiceMenu() } void ServiceMenu::toggle() { - if (define_buttons_ && define_buttons_->isEnabled()) { - return; // No se puede mostrar u ocultar el menu de servicio si se estan definiendo los botones - } + if (define_buttons_ && define_buttons_->isEnabled()) return; + if (isAnimating() && !define_buttons_->isEnabled()) return; // No permitir toggle durante una animación playBackSound(); enabled_ = !enabled_; + if (enabled_) { + // Primero resetea el estado y luego muestra la animación + //reset(); Options::gamepad_manager.assignAndLinkGamepads(); + renderer_->show(this); } else { - reset(); + // Al cerrar, solo inicia la animación de ocultar + renderer_->hide(); + // NO llames a reset() aquí } } void ServiceMenu::render() { - if (!enabled_) { - return; + // Condición corregida: renderiza si está habilitado O si se está animando + if (enabled_ || isAnimating()) { + renderer_->render(this); + } else { + return; // Si no está ni habilitado ni animándose, no dibujes nada. } - renderer_->render(this); - // El mensaje de reinicio se dibuja por encima, así que lo gestionamos aquí - const float MSG_X = param.game.game_area.center_x; - const float MSG_Y = renderer_->getRect().y + 39.0F; - restart_message_ui_->setPosition(MSG_X, MSG_Y); - restart_message_ui_->render(); + // El mensaje de reinicio y otros elementos solo deben aparecer si está completamente visible, + // no durante la animación. + if (enabled_ && !isAnimating()) { + const float MSG_X = param.game.game_area.center_x; + const float MSG_Y = renderer_->getRect().y + 39.0F; + restart_message_ui_->setPosition(MSG_X, MSG_Y); + restart_message_ui_->render(); - // Renderizar DefineButtons si está activo (se dibuja por encima de todo) - if (define_buttons_ && define_buttons_->isEnabled()) { - define_buttons_->render(); + if (define_buttons_ && define_buttons_->isEnabled()) { + define_buttons_->render(); + } } } void ServiceMenu::update() { - if (!enabled_) { - return; - } - + // El renderer siempre se actualiza para manejar sus animaciones renderer_->update(this); + if (!enabled_) return; + + // Lógica de actualización del mensaje de reinicio y botones bool now_pending = Options::pending_changes.has_pending_changes; if (now_pending != last_pending_changes_) { now_pending ? restart_message_ui_->show() : restart_message_ui_->hide(); @@ -84,11 +95,8 @@ void ServiceMenu::update() { } restart_message_ui_->update(); - // Actualizar DefineButtons if (define_buttons_) { define_buttons_->update(); - - // Si DefineButtons ha terminado y está listo para cerrar completamente if (define_buttons_->isEnabled() && define_buttons_->isReadyToClose()) { define_buttons_->disable(); } @@ -102,7 +110,22 @@ void ServiceMenu::reset() { previous_settings_group_ = SettingsGroup::MAIN; initializeOptions(); updateMenu(); - renderer_->setLayout(this); // Notifica al renderer para que calcule el layout inicial + renderer_->setLayout(this); +} + +void ServiceMenu::moveBack() { + // Si estamos en una subpantalla, no llamamos a toggle + if (current_settings_group_ != SettingsGroup::MAIN) { + playBackSound(); + current_settings_group_ = previous_settings_group_; + selected_ = (current_settings_group_ == SettingsGroup::MAIN) ? main_menu_selected_ : 0; + updateMenu(); + return; + } + + // Si estamos en la pantalla principal, llamamos a toggle() para cerrar con animación. + // toggle() ya reproduce el sonido, por lo que no es necesario aquí. + toggle(); } // --- Lógica de Navegación --- @@ -159,17 +182,6 @@ void ServiceMenu::selectOption() { playSelectSound(); } -void ServiceMenu::moveBack() { - playBackSound(); - if (current_settings_group_ == SettingsGroup::MAIN) { - enabled_ = false; - return; - } - current_settings_group_ = previous_settings_group_; - selected_ = (current_settings_group_ == SettingsGroup::MAIN) ? main_menu_selected_ : 0; - updateMenu(); -} - // --- Lógica Interna --- void ServiceMenu::updateDisplayOptions() { @@ -554,16 +566,13 @@ void ServiceMenu::handleEvent(const SDL_Event &event) { } bool ServiceMenu::checkInput() { - if (!enabled_) { - return false; - } - - if (define_buttons_ && define_buttons_->isEnabled()) { + // --- Guardas --- + // No procesar input si el menú no está habilitado, si se está animando o si se definen botones + if (!enabled_ || isAnimating() || (define_buttons_ && define_buttons_->isEnabled())) { return false; } static auto input = Input::get(); - using Action = Input::Action; const std::vector>> actions = { @@ -594,4 +603,13 @@ bool ServiceMenu::checkInput() { } return false; +} + +// --- Nuevo Getter --- +auto ServiceMenu::isAnimating() const -> bool { + return renderer_ && renderer_->isAnimating(); +} + +auto ServiceMenu::isDefiningButtons() const -> bool { + return define_buttons_ && define_buttons_->isEnabled(); } \ No newline at end of file diff --git a/source/ui/service_menu.h b/source/ui/service_menu.h index 90a4273..426b6d0 100644 --- a/source/ui/service_menu.h +++ b/source/ui/service_menu.h @@ -1,35 +1,22 @@ #pragma once -#include // Para size_t -#include // Para unique_ptr -#include // Para basic_string, string -#include // Para pair -#include // Para vector +#include +#include +#include +#include +#include -#include "ui_message.h" // Para UIMessage +#include "ui_message.h" #include "define_buttons.h" class MenuOption; class MenuRenderer; -//class DefineButtons; // Forward declaration class ServiceMenu { public: - enum class SettingsGroup { - CONTROLS, - VIDEO, - AUDIO, - SETTINGS, - SYSTEM, - MAIN - }; - - enum class GroupAlignment { - CENTERED, - LEFT - }; - - // --- Constantes públicas que el Renderer podría necesitar --- + // ... (enums y constantes sin cambios) + enum class SettingsGroup { CONTROLS, VIDEO, AUDIO, SETTINGS, SYSTEM, MAIN }; + enum class GroupAlignment { CENTERED, LEFT }; static constexpr size_t OPTIONS_HORIZONTAL_PADDING = 20; static constexpr size_t MIN_WIDTH = 240; static constexpr size_t MIN_GAP_OPTION_VALUE = 30; @@ -55,17 +42,17 @@ class ServiceMenu { void selectOption(); void moveBack(); - // --- Método para manejar eventos (llamado desde GlobalEvents) --- + // --- Método para manejar eventos --- void handleEvent(const SDL_Event &event); bool checkInput(); - // NUEVO: Getter para saber si DefineButtons está activo - [[nodiscard]] auto isDefiningButtons() const -> bool { - return define_buttons_ && define_buttons_->isEnabled(); - } + // --- Getters para el estado --- + [[nodiscard]] auto isDefiningButtons() const -> bool; + [[nodiscard]] auto isAnimating() const -> bool; // Nuevo getter // --- Getters para que el Renderer pueda leer el estado --- [[nodiscard]] auto isEnabled() const -> bool { return enabled_; } + // ... (resto de getters sin cambios) [[nodiscard]] auto getTitle() const -> const std::string & { return title_; } [[nodiscard]] auto getCurrentGroup() const -> SettingsGroup { return current_settings_group_; } [[nodiscard]] auto getCurrentGroupAlignment() const -> GroupAlignment; @@ -76,26 +63,19 @@ class ServiceMenu { [[nodiscard]] auto countOptionsInGroup(SettingsGroup group) const -> size_t; private: - // --- Lógica de estado del menú (Modelo) --- + // ... (resto de miembros privados sin cambios) bool enabled_ = false; std::vector> options_; std::vector display_options_; std::vector> option_pairs_; - SettingsGroup current_settings_group_; SettingsGroup previous_settings_group_; std::string title_; size_t selected_ = 0; size_t main_menu_selected_ = 0; - - // --- Mensaje de reinicio --- std::unique_ptr restart_message_ui_; bool last_pending_changes_ = false; - - // --- Configuración de botones --- std::unique_ptr define_buttons_; - - // --- La Vista --- std::unique_ptr renderer_; // --- Métodos de lógica interna ---