#include "service_menu.h" #include "screen.h" #include #include #include #include "text.h" #include "resource.h" #include "options.h" #include "section.h" // Para Name, name, Options, options, AttractMode #include "audio.h" #include #include "lang.h" // Singleton ServiceMenu *ServiceMenu::instance_ = nullptr; // Inicializa la instancia única del singleton void ServiceMenu::init() { ServiceMenu::instance_ = new ServiceMenu(); } // Libera la instancia única del singleton void ServiceMenu::destroy() { delete ServiceMenu::instance_; } // Devuelve la instancia única del singleton ServiceMenu *ServiceMenu::get() { return ServiceMenu::instance_; } // Constructor de ServiceMenu ServiceMenu::ServiceMenu() : element_text_(Resource::get()->getText("04b_25_flat")), title_text_(Resource::get()->getText("04b_25_flat_2x")), current_settings_group_(SettingsGroup::MAIN), previous_settings_group_(current_settings_group_) { reset(); } // Alterna la visibilidad del menú de servicio void ServiceMenu::toggle() { enabled_ = !enabled_; if (!enabled_) { reset(); } } // Dibuja el menú de servicio en pantalla void ServiceMenu::render() { if (enabled_) { int y = rect_.y; // SOMBRA 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); } // FONDO 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_); // BORDE SDL_SetRenderDrawColor(Screen::get()->getRenderer(), title_color_.r, title_color_.g, title_color_.b, 255); SDL_RenderRect(Screen::get()->getRenderer(), &rect_); // TITULO y += title_padding_; title_text_->writeDX(TEXT_COLOR | TEXT_CENTER, param.game.game_area.center_x, y, lang::getText("[SERVICE_MENU] TITLE"), -4, title_color_); // LINEA 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 y = options_y_; for (size_t i = 0; i < option_pairs_.size(); ++i) { if (getGroupAlignment(current_settings_group_) == GroupAlignment::LEFT) { // Nombre de la opción element_text_->writeColored(rect_.x + OPTIONS_HORIZONTAL_PADDING_, y, option_pairs_.at(i).first, i == selected_ ? selected_color_ : text_color_, -2); // Valor de la opción 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); } else { // Nombre de la opción 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_; } } } // Actualiza el estado del menú de servicio (colores, animaciones, etc.) void ServiceMenu::update() { if (enabled_) { updateCounter(); selected_color_ = getSelectedColor(); } } // 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 - 1) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); width_ = 240; height_ = upper_height_ + lower_height_; rect_ = { (param.game.width - width_) / 2, (param.game.height - height_) / 2, static_cast(width_), static_cast(height_)}; setOptionsPosition(); } // Establce la posición donde empezar a escribir las opciones del menu void ServiceMenu::setOptionsPosition() { resize(); options_y_ = rect_.y + upper_height_ + lower_padding_; } // Cambia el tamaño de la ventana de menu void ServiceMenu::resize() { lower_height_ = ((display_options_.size() - 1) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2); width_ = 240; height_ = upper_height_ + lower_height_; rect_ = { (param.game.width - width_) / 2, (param.game.height - height_) / 2, static_cast(width_), static_cast(height_)}; } // Actualiza el contador interno para animaciones o efectos visuales void ServiceMenu::updateCounter() { static Uint64 lastUpdate = SDL_GetTicks(); Uint64 currentTicks = SDL_GetTicks(); if (currentTicks - lastUpdate >= 50) { counter_++; lastUpdate = currentTicks; } } // Devuelve el color actual del elemento seleccionado (animado) Color ServiceMenu::getSelectedColor() const { static std::array colors = { Color(0xFF, 0xFB, 0x8A), // Amarillo suave Color(0xFF, 0xE4, 0x5D), // Dorado medio Color(0xFF, 0xD1, 0x3C), // Amarillo pastel intenso Color(0xFF, 0xBF, 0x23), // Amarillo anaranjado Color(0xFF, 0xAA, 0x12), // Amarillo cálido Color(0xE6, 0x9A, 0x08), // Mostaza oscuro Color(0xE6, 0x9A, 0x08), // Mostaza oscuro (regreso, cierre) Color(0xFF, 0xAA, 0x12), // Amarillo cálido (regreso) Color(0xFF, 0xBF, 0x23), // Amarillo anaranjado (regreso) Color(0xFF, 0xD1, 0x3C), // Amarillo pastel intenso (regreso) Color(0xFF, 0xE4, 0x5D), // Dorado medio (regreso) Color(0xFF, 0xFB, 0x8A) // Amarillo suave (regreso) }; const size_t index = counter_ % colors.size(); return colors.at(index); } // Método privado para reproducir el sonido del menú void ServiceMenu::playMenuSound() { Audio::get()->playSound(MENU_SOUND_); } // Mueve el selector hacia arriba en la lista de opciones void ServiceMenu::setSelectorUp() { if (display_options_.empty()) return; selected_ = (selected_ > 0) ? selected_ - 1 : display_options_.size() - 1; playMenuSound(); } // Mueve el selector hacia abajo 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; } if (display_options_.at(selected_)->behavior == OptionBehavior::ADJUST) { display_options_.at(selected_)->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; } // Carpeta if (display_options_.at(selected_)->type == ValueType::FOLDER) { previous_settings_group_ = current_settings_group_; current_settings_group_ = display_options_.at(selected_)->target_group; updateMenu(current_settings_group_); selected_ = 0; setOptionsPosition(); playMenuSound(); return; } // Opción if (display_options_.at(selected_)->behavior == OptionBehavior::SELECT) { if (display_options_.at(selected_)->caption == lang::getText("[SERVICE_MENU] RESET")) { section::name = section::Name::RESET; toggle(); return; } else if (display_options_.at(selected_)->caption == lang::getText("[SERVICE_MENU] QUIT")) { section::name = section::Name::QUIT; section::options = section::Options::NONE; return; } else if (display_options_.at(selected_)->caption == lang::getText("[SERVICE_MENU] SHUTDOWN")) { section::name = section::Name::QUIT; section::options = section::Options::SHUTDOWN; return; } return; } } // 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 { selected_ = 0; current_settings_group_ = previous_settings_group_; updateMenu(current_settings_group_); setOptionsPosition(); playMenuSound(); } } // Inicializa todas las opciones del menú de servicio void ServiceMenu::initializeOptions() { options_.clear(); // Video options_.emplace_back(lang::getText("[SERVICE_MENU] FULLSCREEN"), SettingsGroup::VIDEO, OptionBehavior::ADJUST, &options.video.fullscreen, ValueType::BOOL); options_.emplace_back(lang::getText("[SERVICE_MENU] WINDOW_SIZE"), SettingsGroup::VIDEO, OptionBehavior::ADJUST, &options.window.size, ValueType::INT, 1, options.window.max_size, 1); options_.emplace_back(lang::getText("[SERVICE_MENU] SHADERS"), SettingsGroup::VIDEO, OptionBehavior::ADJUST, &options.video.shaders, ValueType::BOOL); options_.emplace_back(lang::getText("[SERVICE_MENU] VSYNC"), SettingsGroup::VIDEO, OptionBehavior::ADJUST, &options.video.v_sync, ValueType::BOOL); options_.emplace_back(lang::getText("[SERVICE_MENU] INTEGER_SCALE"), SettingsGroup::VIDEO, OptionBehavior::ADJUST, &options.video.integer_scale, ValueType::BOOL); // Audio options_.emplace_back(lang::getText("[SERVICE_MENU] AUDIO"), SettingsGroup::AUDIO, OptionBehavior::ADJUST, &options.audio.enabled, ValueType::BOOL); options_.emplace_back(lang::getText("[SERVICE_MENU] MAIN_VOLUME"), SettingsGroup::AUDIO, OptionBehavior::ADJUST, &options.audio.volume, ValueType::INT, 0, 100, 5); options_.emplace_back(lang::getText("[SERVICE_MENU] MUSIC_VOLUME"), SettingsGroup::AUDIO, OptionBehavior::ADJUST, &options.audio.music.volume, ValueType::INT, 0, 100, 5); options_.emplace_back(lang::getText("[SERVICE_MENU] SFX_VOLUME"), SettingsGroup::AUDIO, OptionBehavior::ADJUST, &options.audio.sound.volume, ValueType::INT, 0, 100, 5); // Game options_.emplace_back(lang::getText("[SERVICE_MENU] AUTOFIRE"), SettingsGroup::GAME, OptionBehavior::ADJUST, &options.game.autofire, ValueType::BOOL); options_.emplace_back( lang::getText("[SERVICE_MENU] LANGUAGE"), SettingsGroup::GAME, OptionBehavior::ADJUST, &options.pending_changes.new_language, std::vector{ lang::getText("[SERVICE_MENU] LANG ES"), lang::getText("[SERVICE_MENU] LANG BA"), lang::getText("[SERVICE_MENU] LANG EN")}); // System options_.emplace_back(lang::getText("[SERVICE_MENU] RESET"), SettingsGroup::SYSTEM, OptionBehavior::SELECT, nullptr, ValueType::NONE); options_.emplace_back(lang::getText("[SERVICE_MENU] QUIT"), SettingsGroup::SYSTEM, OptionBehavior::SELECT, nullptr, ValueType::NONE); if (options.game.shutdown_enabled) options_.emplace_back(lang::getText("[SERVICE_MENU] SHUTDOWN"), SettingsGroup::SYSTEM, OptionBehavior::SELECT, nullptr, ValueType::NONE); // Menu principal options_.emplace_back(lang::getText("[SERVICE_MENU] VIDEO"), SettingsGroup::MAIN, OptionBehavior::SELECT, SettingsGroup::VIDEO); options_.emplace_back(lang::getText("[SERVICE_MENU] AUDIO"), SettingsGroup::MAIN, OptionBehavior::SELECT, SettingsGroup::AUDIO); options_.emplace_back(lang::getText("[SERVICE_MENU] GAME"), SettingsGroup::MAIN, OptionBehavior::SELECT, SettingsGroup::GAME); options_.emplace_back(lang::getText("[SERVICE_MENU] SYSTEM"), SettingsGroup::MAIN, OptionBehavior::SELECT, SettingsGroup::SYSTEM); } // Devuelve las opciones del grupo como pares (nombre, valor) ServiceMenu::OptionPairs ServiceMenu::getOptionPairs(ServiceMenu::SettingsGroup group) const { OptionPairs option_pairs; for (const auto &option : options_) { if (option.group == group) { option_pairs.emplace_back(option.caption, option.getValueAsString()); } } return option_pairs; } // Devuelve las opciones del grupo como un vector de OptionEntry std::vector ServiceMenu::getOptionsByGroup(SettingsGroup group) { std::vector filtered_options; for (auto &option : options_) { if (option.group == group) { filtered_options.push_back(&option); } } return filtered_options; } // Aplica la configuración correspondiente al grupo seleccionado void ServiceMenu::applySettings(ServiceMenu::SettingsGroup group) { switch (group) { case SettingsGroup::VIDEO: Screen::get()->applySettings(); break; case SettingsGroup::AUDIO: Audio::get()->applySettings(); break; case SettingsGroup::GAME: break; default: break; } } // Actualiza las opciones mostradas según el grupo seleccionado void ServiceMenu::updateMenu(SettingsGroup group) { AdjustListValues(); option_pairs_ = getOptionPairs(group); display_options_ = getOptionsByGroup(group); } // Reinicia el menú al estado inicial (grupo principal y opción seleccionada) void ServiceMenu::reset() { selected_ = 0; previous_settings_group_ = current_settings_group_ = SettingsGroup::MAIN; initializeOptions(); updateMenu(current_settings_group_); setAnchors(); } // Calcula la altura total del menú en píxeles int ServiceMenu::calculateMenuHeight() const { return ((4 + findLargestGroupSize() + 1) * options_padding_) - 5; } // Devuelve el tamaño (número de opciones) del grupo más grande int ServiceMenu::findLargestGroupSize() const { std::unordered_map group_counts; for (const auto &option : options_) ++group_counts[option.group]; 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::GAME: return GroupAlignment::LEFT; default: return GroupAlignment::CENTERED; } } // Devuelve un puntero a OptionEntry a partir del caption, o nullptr si no se encuentra ServiceMenu::OptionEntry *ServiceMenu::getOptionEntryByCaption(const std::string &caption) { for (auto &option : options_) { if (option.caption == caption) { return &option; } } return nullptr; } // Ajusta los valores de las opciones tipo lista void ServiceMenu::AdjustListValues() { // Idioma auto option = getOptionEntryByCaption(lang::getText("[SERVICE_MENU] LANGUAGE")); for (size_t i = 0; i < option->value_list.size(); ++i) { if (lang::getCodeFromName(option->value_list[i]) == options.game.language) { option->list_index = i; } } }