diff --git a/source/MenuRenderer.cpp b/source/MenuRenderer.cpp new file mode 100644 index 0000000..ee66039 --- /dev/null +++ b/source/MenuRenderer.cpp @@ -0,0 +1,200 @@ +#include "MenuRenderer.h" +#include "text.h" +#include "MenuOption.h" // Necesario para acceder a las opciones +#include "screen.h" // Para param +#include + +MenuRenderer::MenuRenderer(std::shared_ptr element_text, std::shared_ptr title_text) + : element_text_(std::move(element_text)), title_text_(std::move(title_text)) +{ +} + +void MenuRenderer::render(const ServiceMenu *menu_state) +{ + // Dibuja la sombra + if (menu_state->getAspect() == ServiceMenu::Aspect::ASPECT1) + { + SDL_FRect shadowRect = {rect_.x + 5, rect_.y + 5, rect_.w, rect_.h}; + SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 64); + SDL_RenderFillRect(Screen::get()->getRenderer(), &shadowRect); + } + + // Dibuja el fondo + const Uint8 ALPHA = menu_state->getAspect() == ServiceMenu::Aspect::ASPECT1 ? 255 : 255; + SDL_SetRenderDrawColor(Screen::get()->getRenderer(), bg_color_.r, bg_color_.g, bg_color_.b, ALPHA); + SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_); + + // Dibuja el borde + SDL_SetRenderDrawColor(Screen::get()->getRenderer(), title_color_.r, title_color_.g, title_color_.b, 255); + SDL_RenderRect(Screen::get()->getRenderer(), &rect_); + + // 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, title_color_); + + // Dibuja la línea separadora + y = rect_.y + upper_height_; + SDL_SetRenderDrawColor(Screen::get()->getRenderer(), title_color_.r, title_color_.g, title_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(); + for (size_t i = 0; i < option_pairs.size(); ++i) + { + const bool is_selected = (i == menu_state->getSelectedIndex()); + const Color ¤t_color = is_selected ? selected_color_ : text_color_; + + if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) + { + 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_->lenght(option_pairs.at(i).second, -2); + element_text_->writeColored(X, y, option_pairs.at(i).second, current_color, -2); + } + else + { + element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, option_pairs.at(i).first, -2, current_color); + } + y += options_height_ + options_padding_; + } +} + +void MenuRenderer::update(const ServiceMenu *menu_state) +{ + if (resizing_) + { + updateResizeAnimation(); + } + updateColorCounter(); + selected_color_ = getAnimatedSelectedColor(); +} + +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::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; + } + } + + options_height_ = element_text_->getCharacterSize(); + options_padding_ = 5; + title_height_ = title_text_->getCharacterSize(); + title_padding_ = title_height_ / 2; + upper_height_ = (title_padding_ * 2) + title_height_; + lower_padding_ = (options_padding_ * 3); + lower_height_ = ((max_entries > 0 ? max_entries - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); + + width_ = ServiceMenu::MIN_WIDTH_; + height_ = upper_height_ + lower_height_; + + rect_ = {(param.game.width - width_) / 2.0f, (param.game.height - height_) / 2.0f, (float)width_, (float)height_}; +} + +void MenuRenderer::resize(const ServiceMenu *menu_state) +{ + width_ = getMenuWidthForGroup(menu_state->getCurrentGroup()); + const auto &display_options = menu_state->getDisplayOptions(); + lower_height_ = ((display_options.size() > 0 ? display_options.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); + height_ = upper_height_ + lower_height_; + SDL_FRect new_rect = {(param.game.width - width_) / 2.0f, (param.game.height - height_) / 2.0f, (float)width_, (float)height_}; + + 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; + } + else + { + rect_ = new_rect; + resizing_ = false; + } + options_y_ = new_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_ = rect_anim_to_; + resizing_ = false; + return; + } + float ease = 1 - (1 - t) * (1 - t); + rect_.x = rect_anim_from_.x + (rect_anim_to_.x - rect_anim_from_.x) * ease; + rect_.y = rect_anim_from_.y + (rect_anim_to_.y - rect_anim_from_.y) * ease; + rect_.w = rect_anim_from_.w + (rect_anim_to_.w - rect_anim_from_.w) * ease; + rect_.h = rect_anim_from_.h + (rect_anim_to_.h - rect_anim_from_.h) * ease; +} + +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; + for (const auto &option : all_options) + { + if (option->getGroup() != sg) + continue; + max_option_width = std::max(max_option_width, element_text_->lenght(option->getCaption(), -2)); + if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) + { + max_value_width = std::max(max_value_width, option->getMaxValueWidth(element_text_.get())); + } + } + 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::max((int)ServiceMenu::MIN_WIDTH_, (int)total_width); + } +} + +int MenuRenderer::getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const +{ + return group_menu_widths_[static_cast(group)]; +} + +void MenuRenderer::updateColorCounter() +{ + static Uint64 lastUpdate = SDL_GetTicks(); + Uint64 currentTicks = SDL_GetTicks(); + if (currentTicks - lastUpdate >= 50) + { + color_counter_++; + lastUpdate = currentTicks; + } +} + +Color MenuRenderer::getAnimatedSelectedColor() +{ + static const std::array colors = { + Color(0xFF, 0xFB, 0x8A), Color(0xFF, 0xE4, 0x5D), Color(0xFF, 0xD1, 0x3C), + Color(0xFF, 0xBF, 0x23), Color(0xFF, 0xAA, 0x12), Color(0xE6, 0x9A, 0x08), + Color(0xE6, 0x9A, 0x08), Color(0xFF, 0xAA, 0x12), Color(0xFF, 0xBF, 0x23), + Color(0xFF, 0xD1, 0x3C), Color(0xFF, 0xE4, 0x5D), Color(0xFF, 0xFB, 0x8A)}; + return colors.at(color_counter_ % colors.size()); +} diff --git a/source/MenuRenderer.h b/source/MenuRenderer.h new file mode 100644 index 0000000..27a71c7 --- /dev/null +++ b/source/MenuRenderer.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include "service_menu.h" // Necesario para las enums y para acceder al estado del menú +#include "utils.h" // Para Color + +// Forward declarations +class Text; +class MenuOption; + +class MenuRenderer { +public: + MenuRenderer(std::shared_ptr element_text, std::shared_ptr title_text); + + // Métodos principales de la vista + void render(const ServiceMenu* menu_state); + void update(const ServiceMenu* menu_state); + + // Método para notificar al renderer que el layout puede haber cambiado + void onLayoutChanged(const ServiceMenu* menu_state); + + // Getters + const SDL_FRect& getRect() const { return rect_; } + +private: + // --- Referencias a los renderizadores de texto --- + std::shared_ptr element_text_; + std::shared_ptr title_text_; + + // --- Variables de estado de la vista (layout y animación) --- + SDL_FRect rect_{}; + Color bg_color_ = SERV_MENU_BG_COLOR; + Color title_color_ = SERV_MENU_TITLE_COLOR; + Color text_color_ = SERV_MENU_TEXT_COLOR; + Color selected_color_ = SERV_MENU_SELECTED_COLOR; + size_t width_ = 0; + size_t height_ = 0; + size_t options_height_ = 0; + size_t options_padding_ = 0; + size_t options_y_ = 0; + size_t title_height_ = 0; + size_t title_padding_ = 0; + size_t upper_height_ = 0; + size_t lower_height_ = 0; + size_t lower_padding_ = 0; + Uint32 color_counter_ = 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; + + // --- Anchos precalculados --- + int group_menu_widths_[5]{}; + + // --- Métodos privados de la vista --- + void setAnchors(const ServiceMenu* menu_state); + void resize(const ServiceMenu* menu_state); + void updateResizeAnimation(); + void precalculateMenuWidths(const std::vector>& all_options, const ServiceMenu* menu_state); + int getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const; + Color getAnimatedSelectedColor(); + void updateColorCounter(); +}; diff --git a/source/UIMessage.cpp b/source/UIMessage.cpp index d6a32f2..ee27260 100644 --- a/source/UIMessage.cpp +++ b/source/UIMessage.cpp @@ -7,13 +7,11 @@ UIMessage::UIMessage(std::shared_ptr text_renderer, const std::string &mes : text_renderer_(text_renderer), text_(message_text), color_(color) {} // Muestra el mensaje en la posición base_x, base_y con animación de entrada desde arriba -void UIMessage::show(float base_x, float base_y) +void UIMessage::show() { if (visible_ && target_y_ == 0.0f) return; // Ya está visible y quieto - base_x_ = base_x; - base_y_ = base_y; start_y_ = DESP_; // Empieza 8 píxeles arriba de la posición base target_y_ = 0.0f; // La posición final es la base y_offset_ = start_y_; @@ -92,8 +90,9 @@ bool UIMessage::isVisible() const return visible_; } -// Permite actualizar la posición base Y del mensaje (por ejemplo, si el menú se mueve) -void UIMessage::setBaseY(float new_base_y) +// Permite actualizar la posición del mensaje (por ejemplo, si el menú se mueve) +void UIMessage::setPosition(float new_base_x, float new_base_y) { + base_x_ = new_base_x; base_y_ = new_base_y; } \ No newline at end of file diff --git a/source/UIMessage.h b/source/UIMessage.h index 1aa64ef..f9006ef 100644 --- a/source/UIMessage.h +++ b/source/UIMessage.h @@ -12,8 +12,8 @@ public: // Constructor: recibe el renderizador de texto, el mensaje y el color UIMessage(std::shared_ptr text_renderer, const std::string &message_text, const Color &color); - // Muestra el mensaje en la posición base_x, base_y con animación de entrada - void show(float base_x, float base_y); + // Muestra el mensaje con animación de entrada + void show(); // Oculta el mensaje con animación de salida void hide(); @@ -27,8 +27,8 @@ public: // Indica si el mensaje está visible actualmente bool isVisible() const; - // Permite actualizar la posición base Y del mensaje (por ejemplo, si el menú se mueve) - void setBaseY(float new_base_y); + // Permite actualizar la posición del mensaje (por ejemplo, si el menú se mueve) + void setPosition(float new_base_x, float new_base_y); private: // --- Configuración --- diff --git a/source/service_menu.cpp b/source/service_menu.cpp index a35f3dd..ecec4f1 100644 --- a/source/service_menu.cpp +++ b/source/service_menu.cpp @@ -1,54 +1,188 @@ -// --- Includes --- #include "service_menu.h" +#include "MenuRenderer.h" // <-- Incluido #include "MenuOption.h" -#include "screen.h" -#include -#include -#include "text.h" #include "resource.h" -#include "section.h" #include "audio.h" -#include +#include "section.h" +#include "screen.h" // Para el UIMessage +#include "asset.h" -// --- Singleton --- -ServiceMenu *ServiceMenu::instance_ = nullptr; - -// Inicializa la instancia singleton +// Singleton +ServiceMenu* ServiceMenu::instance_ = nullptr; void ServiceMenu::init() { ServiceMenu::instance_ = new ServiceMenu(); } - -// Libera la instancia singleton void ServiceMenu::destroy() { delete ServiceMenu::instance_; } +ServiceMenu* ServiceMenu::get() { return ServiceMenu::instance_; } -// Devuelve la instancia singleton -ServiceMenu *ServiceMenu::get() { return ServiceMenu::instance_; } - -// --- Constructor y setup --- - -// Constructor: inicializa textos, mensaje UI y estado inicial +// Constructor ServiceMenu::ServiceMenu() - : element_text_(Resource::get()->getText("04b_25_flat")), - title_text_(Resource::get()->getText("04b_25_flat_2x")), - current_settings_group_(SettingsGroup::MAIN), + : current_settings_group_(SettingsGroup::MAIN), previous_settings_group_(current_settings_group_) { - restart_message_ui_ = std::make_unique( - element_text_, - Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), - title_color_); + auto element_text = Resource::get()->getText("04b_25_flat"); + auto title_text = Resource::get()->getText("04b_25_flat_2x"); + + renderer_ = std::make_unique(element_text, title_text); + restart_message_ui_ = std::make_unique(element_text, Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), SERV_MENU_TITLE_COLOR); + reset(); } -// Alterna la visibilidad del menú de servicio. -// Si se desactiva, reinicia el menú al estado inicial. -void ServiceMenu::toggle() -{ +void ServiceMenu::toggle() { enabled_ = !enabled_; - if (!enabled_) - { + if (!enabled_) { reset(); } } +void ServiceMenu::render() { + if (!enabled_) return; + 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(); +} + +void ServiceMenu::update() { + if (!enabled_) return; + + renderer_->update(this); + + 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(); + last_pending_changes_ = now_pending; + } + restart_message_ui_->update(); +} + +void ServiceMenu::reset() { + selected_ = 0; + main_menu_selected_ = 0; + current_settings_group_ = SettingsGroup::MAIN; + previous_settings_group_ = SettingsGroup::MAIN; + initializeOptions(); + updateMenu(); + renderer_->onLayoutChanged(this); // Notifica al renderer para que calcule el layout inicial +} + +// --- Lógica de Navegación --- +void ServiceMenu::setSelectorUp() { + if (display_options_.empty()) return; + selected_ = (selected_ > 0) ? selected_ - 1 : display_options_.size() - 1; + playMenuSound(); +} + +void ServiceMenu::setSelectorDown() { + if (display_options_.empty()) return; + selected_ = (selected_ + 1) % display_options_.size(); + playMenuSound(); +} + +void ServiceMenu::adjustOption(bool adjust_up) { + if (display_options_.empty()) return; + auto& selected_option = display_options_.at(selected_); + if (selected_option->getBehavior() == MenuOption::Behavior::ADJUST) { + selected_option->adjustValue(adjust_up); + applySettings(); + playMenuSound(); + } +} + +void ServiceMenu::selectOption() { + if (display_options_.empty()) return; + if (current_settings_group_ == SettingsGroup::MAIN) main_menu_selected_ = selected_; + auto& selected_option = display_options_.at(selected_); + if (auto folder = dynamic_cast(selected_option)) { + previous_settings_group_ = current_settings_group_; + current_settings_group_ = folder->getTargetGroup(); + selected_ = 0; + updateMenu(); + } else if (selected_option->getBehavior() == MenuOption::Behavior::SELECT) { + selected_option->executeAction(); + } + playMenuSound(); +} + +void ServiceMenu::moveBack() { + 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(); + playMenuSound(); +} + +// --- Lógica Interna --- + +void ServiceMenu::updateMenu() { + title_ = settingsGroupToString(current_settings_group_); + AdjustListValues(); + + // Actualiza las opciones visibles + display_options_.clear(); + for (auto& option : options_) { + if (option->getGroup() == current_settings_group_ && !option->isHidden()) { + display_options_.push_back(option.get()); + } + } + + // Actualiza los pares de strings para el renderer + option_pairs_.clear(); + for (const auto& option : display_options_) { + option_pairs_.emplace_back(option->getCaption(), option->getValueAsString()); + } + + // Notifica al renderer del cambio de layout + renderer_->onLayoutChanged(this); +} + +void ServiceMenu::applySettings() { + if (current_settings_group_ == SettingsGroup::VIDEO) Screen::get()->applySettings(); + if (current_settings_group_ == SettingsGroup::AUDIO) Audio::get()->applySettings(); + if (current_settings_group_ == SettingsGroup::SETTINGS) { + for(auto& option : options_){ + if(option->getCaption() == Lang::getText("[SERVICE_MENU] SHUTDOWN]")){ + option->setHidden(!Options::settings.shutdown_enabled); + updateMenu(); // El menú debe refrescarse si algo se oculta + break; + } + } + } + // Refresca los pares de strings por si un valor cambió + option_pairs_.clear(); + for (const auto& option : display_options_) { + option_pairs_.emplace_back(option->getCaption(), option->getValueAsString()); + } +} + +// --- Getters y otros --- + +ServiceMenu::GroupAlignment ServiceMenu::getCurrentGroupAlignment() const { + switch (current_settings_group_) { + case SettingsGroup::VIDEO: + case SettingsGroup::AUDIO: + case SettingsGroup::SETTINGS: + return GroupAlignment::LEFT; + default: + return GroupAlignment::CENTERED; + } +} + +size_t ServiceMenu::countOptionsInGroup(SettingsGroup group) const { + size_t count = 0; + for (const auto& option : options_) { + if (option->getGroup() == group && !option->isHidden()) { + count++; + } + } + return count; +} + // Inicializa todas las opciones del menú void ServiceMenu::initializeOptions() { @@ -91,328 +225,24 @@ void ServiceMenu::initializeOptions() options_.push_back(std::make_unique(Lang::getText("[SERVICE_MENU] SETTINGS"), SettingsGroup::MAIN, SettingsGroup::SETTINGS)); options_.push_back(std::make_unique(Lang::getText("[SERVICE_MENU] SYSTEM"), SettingsGroup::MAIN, SettingsGroup::SYSTEM)); - precalculateMenuWidths(); + //precalculateMenuWidths(); } -// Reinicia el menú al estado inicial -void ServiceMenu::reset() +// Sincroniza los valores de las opciones tipo lista +void ServiceMenu::AdjustListValues() { - selected_ = 0; - previous_settings_group_ = current_settings_group_ = SettingsGroup::MAIN; - title_ = settingsGroupToString(current_settings_group_); - initializeOptions(); - updateMenu(current_settings_group_); - setAnchors(); -} - -// --- Métodos de navegación y selección --- - -// Sube el selector una posición en la lista de opciones -void ServiceMenu::setSelectorUp() -{ - if (display_options_.empty()) - return; - selected_ = (selected_ > 0) ? selected_ - 1 : display_options_.size() - 1; - playMenuSound(); -} - -// Baja el selector una posición en la lista de opciones -void ServiceMenu::setSelectorDown() -{ - if (display_options_.empty()) - return; - selected_ = (selected_ + 1) % display_options_.size(); - playMenuSound(); -} - -// Ajusta el valor de la opción seleccionada si es ajustable -void ServiceMenu::adjustOption(bool adjust_up) -{ - if (display_options_.empty() || selected_ >= display_options_.size()) - return; - - auto &selected_option = display_options_.at(selected_); - if (selected_option->getBehavior() == MenuOption::Behavior::ADJUST) + for (auto &option : options_) { - selected_option->adjustValue(adjust_up); - option_pairs_ = getOptionPairs(current_settings_group_); - applySettings(current_settings_group_); - playMenuSound(); - } -} - -// Ejecuta la acción de la opción seleccionada o navega a un submenú -void ServiceMenu::selectOption() -{ - if (display_options_.empty() || selected_ >= display_options_.size()) - return; - - // Guarda la selección en el menú principal - if (current_settings_group_ == SettingsGroup::MAIN) - main_menu_selected_ = selected_; - - auto &selected_option = display_options_.at(selected_); - - // Si es una carpeta, navega al subgrupo correspondiente - if (auto folder = dynamic_cast(selected_option)) - { - previous_settings_group_ = current_settings_group_; - current_settings_group_ = folder->getTargetGroup(); - title_ = settingsGroupToString(current_settings_group_); - updateMenu(current_settings_group_); - selected_ = 0; - setOptionsPosition(); - playMenuSound(); - return; - } - - // Si es una acción, ejecútala - if (selected_option->getBehavior() == MenuOption::Behavior::SELECT) - { - selected_option->executeAction(); - } -} - -// Vuelve al grupo de opciones anterior o cierra el menú si está en el principal -void ServiceMenu::moveBack() -{ - if (current_settings_group_ == SettingsGroup::MAIN) - { - enabled_ = false; - return; - } - else - { - if (previous_settings_group_ == SettingsGroup::MAIN) - selected_ = main_menu_selected_; - else - selected_ = 0; - current_settings_group_ = previous_settings_group_; - updateMenu(current_settings_group_); - setOptionsPosition(); - playMenuSound(); - } -} - -// --- Métodos de renderizado --- - -// Dibuja el menú de servicio y el mensaje de reinicio si corresponde -void ServiceMenu::render() -{ - if (!enabled_) - return; - - // --- Fondo y marco del menú --- - if (aspect_ == Aspect::ASPECT1) - { - SDL_FRect shadowRect = {rect_.x + 5, rect_.y + 5, rect_.w, rect_.h}; - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 64); - SDL_RenderFillRect(Screen::get()->getRenderer(), &shadowRect); - } - const Uint8 ALPHA = aspect_ == Aspect::ASPECT1 ? 255 : 255; - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), bg_color_.r, bg_color_.g, bg_color_.b, ALPHA); - SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_); - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), title_color_.r, title_color_.g, title_color_.b, 255); - SDL_RenderRect(Screen::get()->getRenderer(), &rect_); - - // --- Mensaje de reinicio (animado) --- - restart_message_ui_->render(); - - // --- Título del menú --- - int y = rect_.y + title_padding_; - title_text_->writeDX(TEXT_COLOR | TEXT_CENTER, param.game.game_area.center_x, y, title_, -4, title_color_); - - // --- Línea separadora --- - y = rect_.y + upper_height_; - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), title_color_.r, title_color_.g, title_color_.b, 255); - SDL_RenderLine(Screen::get()->getRenderer(), rect_.x + OPTIONS_HORIZONTAL_PADDING_, y, rect_.x + rect_.w - OPTIONS_HORIZONTAL_PADDING_, y); - - // --- Opciones del menú --- - y = options_y_; - for (size_t i = 0; i < option_pairs_.size(); ++i) - { - if (getGroupAlignment(current_settings_group_) == GroupAlignment::LEFT) + if (auto list_option = dynamic_cast(option.get())) { - // Opción alineada a la izquierda - element_text_->writeColored(rect_.x + OPTIONS_HORIZONTAL_PADDING_, y, option_pairs_.at(i).first, i == selected_ ? selected_color_ : text_color_, -2); - const int X = rect_.x + rect_.w - OPTIONS_HORIZONTAL_PADDING_ - element_text_->lenght(std::string(option_pairs_.at(i).second), -2); - element_text_->writeColored(X, y, std::string(option_pairs_.at(i).second), i == selected_ ? selected_color_ : text_color_, -2); + list_option->sync(); } - else - { - // Opción centrada - element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, option_pairs_.at(i).first, -2, i == selected_ ? selected_color_ : text_color_); - } - y += options_height_ + options_padding_; } } -// --- Métodos de actualización y animación --- - -// Actualiza el estado del menú y la animación del mensaje de reinicio -void ServiceMenu::update() -{ - if (resizing_) - { - updateResizeAnimation(); - return; - } - if (enabled_) - { - updateCounter(); - selected_color_ = getSelectedColor(); - } - - // Detecta cambios en el estado de pending_changes y muestra/oculta el mensaje animado - bool now_pending = Options::pending_changes.has_pending_changes; - if (now_pending != last_pending_changes_) - { - if (now_pending) - { - const float MSG_X = param.game.game_area.center_x; - const float MSG_Y = rect_.y + RESET_TEXT_POS_Y_; - restart_message_ui_->show(MSG_X, MSG_Y); - } - else - { - restart_message_ui_->hide(); - } - last_pending_changes_ = now_pending; - } - // Actualiza la animación del mensaje - restart_message_ui_->update(); -} - -// --- Métodos de layout y animación del menú --- - -// Calcula y establece los anclajes y dimensiones del menú -void ServiceMenu::setAnchors() -{ - const size_t MAX_ENTRIES = findLargestGroupSize(); - options_height_ = element_text_->getCharacterSize(); - options_padding_ = 5; - title_height_ = title_text_->getCharacterSize(); - title_padding_ = title_height_ / 2; - upper_height_ = (title_padding_ * 2) + title_height_; - lower_padding_ = (options_padding_ * 3); - lower_height_ = ((MAX_ENTRIES > 0 ? MAX_ENTRIES - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); - width_ = 240; - height_ = upper_height_ + lower_height_; - rect_ = {(param.game.width - width_) / 2.0f, (param.game.height - height_) / 2.0f, static_cast(width_), static_cast(height_)}; - setOptionsPosition(); -} - -// Calcula la posición vertical de las opciones -void ServiceMenu::setOptionsPosition() -{ - resize(); - SDL_FRect new_rect = {(param.game.width - width_) / 2.0f, (param.game.height - height_) / 2.0f, static_cast(width_), static_cast(height_)}; - options_y_ = new_rect.y + upper_height_ + lower_padding_; -} - -// Redimensiona el menú y prepara la animación si es necesario -void ServiceMenu::resize() -{ - int menu_width = getMenuWidthForGroup(current_settings_group_); - width_ = menu_width; - lower_height_ = ((display_options_.size() > 0 ? display_options_.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); - height_ = upper_height_ + lower_height_; - SDL_FRect new_rect = {(param.game.width - width_) / 2.0f, (param.game.height - height_) / 2.0f, static_cast(width_), static_cast(height_)}; - 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; - } - else - { - rect_ = new_rect; - resizing_ = false; - } -} - -// Actualiza la animación de resize del menú y la posición del mensaje de reinicio -void ServiceMenu::updateResizeAnimation() -{ - if (!resizing_) - return; - ++resize_anim_step_; - float t = static_cast(resize_anim_step_) / resize_anim_steps_; - if (t >= 1.0f) - { - rect_ = rect_anim_to_; - resizing_ = false; - restart_message_ui_->setBaseY(rect_.y + RESET_TEXT_POS_Y_); - return; - } - float ease = 1 - (1 - t) * (1 - t); - rect_.x = rect_anim_from_.x + (rect_anim_to_.x - rect_anim_from_.x) * ease; - rect_.y = rect_anim_from_.y + (rect_anim_to_.y - rect_anim_from_.y) * ease; - rect_.w = rect_anim_from_.w + (rect_anim_to_.w - rect_anim_from_.w) * ease; - rect_.h = rect_anim_from_.h + (rect_anim_to_.h - rect_anim_from_.h) * ease; -} - -// --- Métodos utilitarios --- - -// Actualiza el contador interno para animaciones de color -void ServiceMenu::updateCounter() -{ - static Uint64 lastUpdate = SDL_GetTicks(); - Uint64 currentTicks = SDL_GetTicks(); - if (currentTicks - lastUpdate >= 50) - { - counter_++; - lastUpdate = currentTicks; - } -} - -// Devuelve el color animado del elemento seleccionado -Color ServiceMenu::getSelectedColor() const -{ - static std::array colors = {Color(0xFF, 0xFB, 0x8A), Color(0xFF, 0xE4, 0x5D), Color(0xFF, 0xD1, 0x3C), Color(0xFF, 0xBF, 0x23), Color(0xFF, 0xAA, 0x12), Color(0xE6, 0x9A, 0x08), Color(0xE6, 0x9A, 0x08), Color(0xFF, 0xAA, 0x12), Color(0xFF, 0xBF, 0x23), Color(0xFF, 0xD1, 0x3C), Color(0xFF, 0xE4, 0x5D), Color(0xFF, 0xFB, 0x8A)}; - return colors.at(counter_ % colors.size()); -} // Reproduce el sonido de navegación del menú -void ServiceMenu::playMenuSound() { Audio::get()->playSound(MENU_SOUND_); } - -// Actualiza el menú según el grupo seleccionado -void ServiceMenu::updateMenu(SettingsGroup group) -{ - title_ = settingsGroupToString(group); - AdjustListValues(); - option_pairs_ = getOptionPairs(group); - display_options_ = getOptionsByGroup(group); - resize(); -} - -// Devuelve el tamaño del grupo con más opciones -int ServiceMenu::findLargestGroupSize() const -{ - std::unordered_map group_counts; - for (const auto &option : options_) - ++group_counts[option->getGroup()]; - int max_size = 0; - for (const auto &pair : group_counts) - if (pair.second > max_size) - max_size = pair.second; - return max_size; -} - -// Devuelve la alineación de las opciones para el grupo dado -ServiceMenu::GroupAlignment ServiceMenu::getGroupAlignment(SettingsGroup group) const -{ - switch (group) - { - case SettingsGroup::VIDEO: - case SettingsGroup::AUDIO: - case SettingsGroup::SETTINGS: - return GroupAlignment::LEFT; - default: - return GroupAlignment::CENTERED; - } -} +void ServiceMenu::playMenuSound() { Audio::get()->playSound(Asset::get()->get("clock.wav")); } // Devuelve el nombre del grupo como string para el título std::string ServiceMenu::settingsGroupToString(SettingsGroup group) const @@ -434,115 +264,3 @@ std::string ServiceMenu::settingsGroupToString(SettingsGroup group) const } } -// Devuelve las opciones del grupo como pares (nombre, valor) -ServiceMenu::OptionPairs ServiceMenu::getOptionPairs(SettingsGroup group) const -{ - OptionPairs option_pairs; - for (const auto &option : options_) - { - if (option->getGroup() == group && !option->isHidden()) - { - option_pairs.emplace_back(option->getCaption(), option->getValueAsString()); - } - } - return option_pairs; -} - -// Devuelve las opciones del grupo como un vector de punteros -std::vector ServiceMenu::getOptionsByGroup(SettingsGroup group) -{ - std::vector filtered_options; - for (auto &option : options_) - { - if (option->getGroup() == group && !option->isHidden()) - { - filtered_options.push_back(option.get()); - } - } - return filtered_options; -} - -// Busca una opción por su caption -MenuOption *ServiceMenu::getOptionByCaption(const std::string &caption) -{ - for (auto &option : options_) - { - if (option->getCaption() == caption) - { - return option.get(); - } - } - return nullptr; -} - -// Aplica la configuración del grupo seleccionado -void ServiceMenu::applySettings(SettingsGroup group) -{ - if (group == SettingsGroup::VIDEO) - Screen::get()->applySettings(); - if (group == SettingsGroup::AUDIO) - Audio::get()->applySettings(); - if (group == SettingsGroup::SETTINGS) - { - auto option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHUTDOWN")); - if (option) - { - option->setHidden(!Options::settings.shutdown_enabled); - display_options_ = getOptionsByGroup(group); - } - } -} - -// Sincroniza los valores de las opciones tipo lista -void ServiceMenu::AdjustListValues() -{ - for (auto &option : options_) - { - if (auto list_option = dynamic_cast(option.get())) - { - list_option->sync(); - } - } -} - -// --- Métodos de cálculo de ancho de menú --- - -// Calcula el ancho óptimo para cada grupo de menú -void ServiceMenu::precalculateMenuWidths() -{ - for (int &w : group_menu_widths_) - w = MIN_WIDTH_; - - for (int group = 0; group < 5; ++group) - { - SettingsGroup sg = static_cast(group); - int max_option_width = 0; - int max_value_width = 0; - - for (const auto &option : options_) - { - if (option->getGroup() != sg) - continue; - - max_option_width = std::max(max_option_width, element_text_->lenght(option->getCaption(), -2)); - max_value_width = std::max(max_value_width, option->getMaxValueWidth(element_text_.get())); - } - - if (getGroupAlignment(sg) == GroupAlignment::LEFT) - { - size_t total_width = max_option_width + MIN_GAP_OPTION_VALUE_ + max_value_width + (OPTIONS_HORIZONTAL_PADDING_ * 2); - group_menu_widths_[group] = std::max(static_cast(MIN_WIDTH_), static_cast(total_width)); - } - else - { - size_t total_width = max_option_width + (OPTIONS_HORIZONTAL_PADDING_ * 2); - group_menu_widths_[group] = std::max(static_cast(MIN_WIDTH_), static_cast(total_width)); - } - } -} - -// Devuelve el ancho precalculado para un grupo de menú -int ServiceMenu::getMenuWidthForGroup(SettingsGroup group) const -{ - return group_menu_widths_[static_cast(group)]; -} diff --git a/source/service_menu.h b/source/service_menu.h index c8c1cb2..4ef6ec7 100644 --- a/source/service_menu.h +++ b/source/service_menu.h @@ -4,146 +4,86 @@ #include #include #include -#include #include "utils.h" #include "UIMessage.h" -// --- Forward Declarations --- +// Forward Declarations class Text; -class MenuOption; // <-- Añadido +class MenuOption; +class MenuRenderer; // <-- Nuevo -class ServiceMenu -{ +class ServiceMenu { public: - // Enums públicas para que MenuOption.h pueda usarlas. - enum class Aspect - { - ASPECT1, - ASPECT2 - }; - enum class SettingsGroup - { - VIDEO, - AUDIO, - SETTINGS, - SYSTEM, - MAIN - }; - enum class GroupAlignment - { - CENTERED, - LEFT - }; + enum class Aspect { ASPECT1, ASPECT2 }; + enum class SettingsGroup { VIDEO, AUDIO, SETTINGS, SYSTEM, MAIN }; + enum class GroupAlignment { CENTERED, LEFT }; + + // --- Constantes públicas que el Renderer podría necesitar --- + static constexpr size_t OPTIONS_HORIZONTAL_PADDING_ = 20; + static constexpr size_t MIN_WIDTH_ = 240; + static constexpr size_t MIN_GAP_OPTION_VALUE_ = 30; static void init(); static void destroy(); - static ServiceMenu *get(); + static ServiceMenu* get(); + void toggle(); - void render(); - void update(); + void render(); // Ahora solo delega + void update(); // Ahora delega la parte visual void reset(); + + // --- Lógica de navegación --- void setSelectorUp(); void setSelectorDown(); void adjustOption(bool adjust_up); void selectOption(); void moveBack(); + + // --- Getters para que el Renderer pueda leer el estado --- bool isEnabled() const { return enabled_; } - void setResizeAnimationSteps(int steps) { resize_anim_steps_ = steps; } + const std::string& getTitle() const { return title_; } + SettingsGroup getCurrentGroup() const { return current_settings_group_; } + GroupAlignment getCurrentGroupAlignment() const; + const std::vector& getDisplayOptions() const { return display_options_; } + const std::vector>& getAllOptions() const { return options_; } + size_t getSelectedIndex() const { return selected_; } + const std::vector>& getOptionPairs() const { return option_pairs_; } + size_t countOptionsInGroup(SettingsGroup group) const; + Aspect getAspect() const { return aspect_; } private: - // Definición de las constantes estáticas - static constexpr const char *MENU_SOUND_ = "clock.wav"; - static constexpr size_t OPTIONS_HORIZONTAL_PADDING_ = 20; - static constexpr size_t MIN_WIDTH_ = 220; - static constexpr size_t MIN_GAP_OPTION_VALUE_ = 20; - static constexpr float RESET_TEXT_POS_Y_ = 39.0f; - - // --- Tipos internos --- - using OptionPairs = std::vector>; - - // --- Variables internas --- + // --- Lógica de estado del menú (Modelo) --- bool enabled_ = false; - SDL_FRect rect_; - std::shared_ptr element_text_; - std::shared_ptr title_text_; - size_t selected_ = 0; - Uint32 counter_ = 0; - - // Usamos punteros únicos para gestionar la vida de los objetos polimórficos. std::vector> options_; - // Mostramos punteros raw (no propietarios) de las opciones filtradas. - std::vector display_options_; - - OptionPairs option_pairs_; + std::vector display_options_; + std::vector> option_pairs_; + SettingsGroup current_settings_group_; SettingsGroup previous_settings_group_; Aspect aspect_ = Aspect::ASPECT1; std::string title_; + size_t selected_ = 0; size_t main_menu_selected_ = 0; - // Variables de aspecto y layout... - Color bg_color_ = SERV_MENU_BG_COLOR; - Color title_color_ = SERV_MENU_TITLE_COLOR; - Color text_color_ = SERV_MENU_TEXT_COLOR; - Color selected_color_ = SERV_MENU_SELECTED_COLOR; - size_t width_; - size_t height_; - size_t options_height_; - size_t options_padding_; - size_t options_y_; - size_t title_height_; - size_t title_padding_; - size_t upper_height_; - size_t lower_height_; - size_t lower_padding_; - - // Variables de 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; - - // Mensaje de reinicio + // --- Mensaje de reinicio --- std::unique_ptr restart_message_ui_; bool last_pending_changes_ = false; - // Anchos precalculados - int group_menu_widths_[5]; + // --- La Vista --- + std::unique_ptr renderer_; - // --- Métodos internos --- - - // Anclaje y aspecto - void setAnchors(); - Color getSelectedColor() const; - void setOptionsPosition(); - void resize(); - void updateResizeAnimation(); - - // Gestión de opciones + // --- Métodos de lógica interna --- void initializeOptions(); - OptionPairs getOptionPairs(SettingsGroup group) const; - std::vector getOptionsByGroup(SettingsGroup group); - MenuOption *getOptionByCaption(const std::string &caption); - - // Lógica de menú - void applySettings(SettingsGroup group); - void updateMenu(SettingsGroup group); + void updateMenu(); + void applySettings(); void AdjustListValues(); - - // Utilidades - void updateCounter(); - int findLargestGroupSize() const; - GroupAlignment getGroupAlignment(SettingsGroup group) const; - std::string settingsGroupToString(SettingsGroup group) const; - void precalculateMenuWidths(); - int getMenuWidthForGroup(SettingsGroup group) const; void playMenuSound(); + std::string settingsGroupToString(SettingsGroup group) const; - // --- Patrón Singleton --- + // --- Singleton --- ServiceMenu(); ~ServiceMenu() = default; - ServiceMenu(const ServiceMenu &) = delete; - ServiceMenu &operator=(const ServiceMenu &) = delete; - static ServiceMenu *instance_; + ServiceMenu(const ServiceMenu&) = delete; + ServiceMenu& operator=(const ServiceMenu&) = delete; + static ServiceMenu* instance_; };