// SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition // GuiCollectionSystemsOptions.cpp // // User interface for the game collection settings. // Submenu to the GuiMenu main menu. // #include "guis/GuiCollectionSystemsOptions.h" #include "CollectionSystemsManager.h" #include "components/OptionListComponent.h" #include "components/SwitchComponent.h" #include "guis/GuiMsgBox.h" #include "guis/GuiSettings.h" #include "guis/GuiTextEditKeyboardPopup.h" #include "guis/GuiTextEditPopup.h" #include "utils/StringUtil.h" #include "views/ViewController.h" GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title) : GuiSettings {title} , mAddedCustomCollection {false} , mDeletedCustomCollection {false} { // Finish editing custom collection. if (CollectionSystemsManager::getInstance()->isEditing()) { ComponentListRow row; row.addElement( std::make_shared( "FINISH EDITING '" + Utils::String::toUpper( CollectionSystemsManager::getInstance()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); row.makeAcceptInputHandler([this] { CollectionSystemsManager::getInstance()->exitEditMode(); mWindow->invalidateCachedBackground(); delete this; }); addRow(row); } // Automatic collections. mCollectionSystemsAuto = std::make_shared>( getHelpStyle(), "SELECT COLLECTIONS", true); std::map autoSystems { CollectionSystemsManager::getInstance()->getAutoCollectionSystems()}; // Add automatic systems. for (std::map::const_iterator it = autoSystems.cbegin(); it != autoSystems.cend(); ++it) mCollectionSystemsAuto->add(it->second.decl.fullName, it->second.decl.name, it->second.isEnabled); addWithLabel("AUTOMATIC GAME COLLECTIONS", mCollectionSystemsAuto); addSaveFunc([this, autoSystems] { std::string autoSystemsSelected {Utils::String::vectorToDelimitedString( mCollectionSystemsAuto->getSelectedObjects(), ",", true)}; std::string autoSystemsConfig {Settings::getInstance()->getString("CollectionSystemsAuto")}; if (autoSystemsSelected != autoSystemsConfig) { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); Settings::getInstance()->setString("CollectionSystemsAuto", autoSystemsSelected); // Check if any systems have been enabled, and if so repopulate them, which results in // a complete initialization of their content. This is necessary as collections aren't // updated while they are disabled. std::vector addedAutoSystems; if (autoSystemsConfig == "") { addedAutoSystems = Utils::String::delimitedStringToVector(autoSystemsSelected, ","); } else if (autoSystemsSelected != "") { std::vector selectedVector { Utils::String::delimitedStringToVector(autoSystemsSelected, ",")}; std::vector configuredVector { Utils::String::delimitedStringToVector(autoSystemsConfig, ",")}; for (std::string system : selectedVector) { if (std::find(configuredVector.begin(), configuredVector.end(), system) == configuredVector.end()) addedAutoSystems.push_back(system); } } if (!addedAutoSystems.empty()) { for (std::string system : addedAutoSystems) CollectionSystemsManager::getInstance()->repopulateCollection( autoSystems.find(system)->second.system); } setNeedsSaving(); setNeedsReloading(); setNeedsCollectionsUpdate(); } }); // Custom collections. mCollectionSystemsCustom = std::make_shared>( getHelpStyle(), "SELECT COLLECTIONS", true); std::map customSystems { CollectionSystemsManager::getInstance()->getCustomCollectionSystems()}; // Add custom systems. for (std::map::const_iterator it = customSystems.cbegin(); it != customSystems.cend(); ++it) mCollectionSystemsCustom->add(it->second.decl.fullName, it->second.decl.name, it->second.isEnabled); addWithLabel("CUSTOM GAME COLLECTIONS", mCollectionSystemsCustom); addSaveFunc([this, customSystems] { if (!mDeletedCustomCollection) { std::string customSystemsSelected {Utils::String::vectorToDelimitedString( mCollectionSystemsCustom->getSelectedObjects(), ",", true)}; std::string customSystemsConfig { Settings::getInstance()->getString("CollectionSystemsCustom")}; if (customSystemsSelected != customSystemsConfig) { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); Settings::getInstance()->setString("CollectionSystemsCustom", customSystemsSelected); // Check if any systems have been enabled, and if so repopulate them, which // results in a complete initialization of their content. This is necessary as // collections aren't updated while they are disabled. std::vector addedCustomSystems; if (customSystemsConfig == "") { addedCustomSystems = Utils::String::delimitedStringToVector(customSystemsSelected, ","); } else if (customSystemsSelected != "") { std::vector selectedVector { Utils::String::delimitedStringToVector(customSystemsSelected, ",")}; std::vector configuredVector { Utils::String::delimitedStringToVector(customSystemsConfig, ",")}; for (std::string system : selectedVector) { if (std::find(configuredVector.begin(), configuredVector.end(), system) == configuredVector.end()) addedCustomSystems.push_back(system); } } if (!mAddedCustomCollection && !addedCustomSystems.empty()) { for (std::string system : addedCustomSystems) CollectionSystemsManager::getInstance()->repopulateCollection( customSystems.find(system)->second.system); } setNeedsSaving(); setNeedsReloading(); setNeedsCollectionsUpdate(); setNeedsGoToGroupedCollections(); } } }); // If there are no custom collections, then gray out this menu entry. if (customSystems.empty()) { mCollectionSystemsCustom->setEnabled(false); mCollectionSystemsCustom->setOpacity(DISABLED_OPACITY); mCollectionSystemsCustom->getParent() ->getChild(mCollectionSystemsCustom->getChildIndex() - 1) ->setOpacity(DISABLED_OPACITY); } // Create custom collection from theme. std::vector unusedFolders { CollectionSystemsManager::getInstance()->getUnusedSystemsFromTheme()}; if (unusedFolders.size() > 0) { ComponentListRow row; auto themeCollection = std::make_shared( "CREATE NEW CUSTOM COLLECTION FROM THEME", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); auto bracketThemeCollection = std::make_shared(); bracketThemeCollection->setResize( glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); bracketThemeCollection->setImage(":/graphics/arrow.svg"); row.addElement(themeCollection, true); row.addElement(bracketThemeCollection, false); row.makeAcceptInputHandler([this, unusedFolders] { auto ss = new GuiSettings("SELECT THEME FOLDER"); std::shared_ptr> folderThemes { std::make_shared>(getHelpStyle(), "SELECT THEME FOLDER", true)}; // Add custom systems. for (auto it = unusedFolders.cbegin(); it != unusedFolders.cend(); ++it) { ComponentListRow row; std::string name {*it}; std::function createCollectionCall { [this, name] { createCustomCollection(name); }}; row.makeAcceptInputHandler(createCollectionCall); auto themeFolder = std::make_shared( Utils::String::toUpper(name), Font::get(FONT_SIZE_SMALL), 0x777777FF); themeFolder->setSelectable(true); row.addElement(themeFolder, true); ss->addRow(row); } mWindow->pushGui(ss); }); addRow(row); } // Create new custom collection. ComponentListRow row; auto newCollection = std::make_shared("CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); auto bracketNewCollection = std::make_shared(); bracketNewCollection->setResize( glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); bracketNewCollection->setImage(":/graphics/arrow.svg"); row.addElement(newCollection, true); row.addElement(bracketNewCollection, false); auto createCollectionCall = [this](const std::string& newVal) { std::string name {newVal}; // We need to store the first GUI and remove it, as it'll be deleted // by the actual GUI. Window* window {mWindow}; GuiComponent* topGui {window->peekGui()}; window->removeGui(topGui); createCustomCollection(name); }; if (Settings::getInstance()->getBool("VirtualKeyboard")) { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditKeyboardPopup(getHelpStyle(), "New Collection Name", "", createCollectionCall, false, "CREATE", "CREATE COLLECTION?")); }); } else { row.makeAcceptInputHandler([this, createCollectionCall] { mWindow->pushGui(new GuiTextEditPopup(getHelpStyle(), "New Collection Name", "", createCollectionCall, false, "CREATE", "CREATE COLLECTION?")); }); } addRow(row); // Delete custom collection. row.elements.clear(); auto deleteCollection = std::make_shared( "DELETE CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF); auto bracketDeleteCollection = std::make_shared(); bracketDeleteCollection->setResize( glm::vec2 {0.0f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()}); bracketDeleteCollection->setImage(":/graphics/arrow.svg"); row.addElement(deleteCollection, true); row.addElement(bracketDeleteCollection, false); row.makeAcceptInputHandler([this, customSystems] { auto ss = new GuiSettings("SELECT COLLECTION TO DELETE"); std::shared_ptr> customCollections { std::make_shared>(getHelpStyle(), "", true)}; for (std::map::const_iterator it = customSystems.cbegin(); it != customSystems.cend(); ++it) { ComponentListRow row; std::string name {(*it).first}; std::function deleteCollectionCall = [this, name] { mWindow->pushGui(new GuiMsgBox( getHelpStyle(), "THIS WILL PERMANENTLY\nDELETE THE COLLECTION\n'" + Utils::String::toUpper(name) + "'\n" "ARE YOU SURE?", "YES", [this, name] { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); mDeletedCustomCollection = true; std::vector selectedCustomCollections { mCollectionSystemsCustom->getSelectedObjects()}; std::string collectionsConfigEntry; // Create the configuration file entry. If the collection to be // deleted was activated, then exclude it. for (auto it = selectedCustomCollections.begin(); it != selectedCustomCollections.end(); ++it) { if ((*it) != name) { if ((*it) != selectedCustomCollections.front() && collectionsConfigEntry != "") collectionsConfigEntry += ","; collectionsConfigEntry += (*it); } } // If the system to be deleted was present in es_settings.xml, we // need to re-write it. if (collectionsConfigEntry != Settings::getInstance()->getString("CollectionSystemsCustom")) { Settings::getInstance()->setString("CollectionSystemsCustom", collectionsConfigEntry); if (selectedCustomCollections.size() == 1 && Settings::getInstance()->getString("StartupSystem") == "collections") Settings::getInstance()->setString("StartupSystem", ""); setNeedsSaving(); setNeedsGoToStart(); } CollectionSystemsManager::getInstance()->deleteCustomCollection(name); return true; }, "NO", [] { return false; })); }; row.makeAcceptInputHandler(deleteCollectionCall); auto customCollection = std::make_shared( Utils::String::toUpper(name), Font::get(FONT_SIZE_MEDIUM), 0x777777FF); customCollection->setSelectable(true); row.addElement(customCollection, true); ss->addRow(row); } // Make the menu slightly wider to fit the scroll indicators. glm::vec2 menuSize {ss->getMenuSize()}; glm::vec3 menuPos {ss->getMenuPosition()}; ss->setMenuSize(glm::vec2 {menuSize.x * 1.08f, menuSize.y}); menuPos.x = (Renderer::getScreenWidth() - ss->getMenuSize().x) / 2.0f; ss->setMenuPosition(menuPos); mWindow->pushGui(ss); }); addRow(row); // Sort favorites on top for custom collections. auto fav_first_custom = std::make_shared(); fav_first_custom->setState(Settings::getInstance()->getBool("FavFirstCustom")); addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", fav_first_custom); addSaveFunc([this, fav_first_custom] { if (fav_first_custom->getState() != Settings::getInstance()->getBool("FavFirstCustom")) { Settings::getInstance()->setBool("FavFirstCustom", fav_first_custom->getState()); setNeedsSaving(); setNeedsReloading(); setNeedsSorting(); setNeedsSortingCollections(); setInvalidateCachedBackground(); } }); // Display star markings for custom collections. auto fav_star_custom = std::make_shared(); fav_star_custom->setState(Settings::getInstance()->getBool("FavStarCustom")); addWithLabel("DISPLAY STAR MARKINGS FOR CUSTOM COLLECTIONS", fav_star_custom); addSaveFunc([this, fav_star_custom] { if (fav_star_custom->getState() != Settings::getInstance()->getBool("FavStarCustom")) { Settings::getInstance()->setBool("FavStarCustom", fav_star_custom->getState()); setNeedsSaving(); setNeedsReloading(); setInvalidateCachedBackground(); } }); // Group unthemed custom collections. auto use_custom_collections_system = std::make_shared(); use_custom_collections_system->setState( Settings::getInstance()->getBool("UseCustomCollectionsSystem")); addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", use_custom_collections_system); addSaveFunc([this, use_custom_collections_system] { if (use_custom_collections_system->getState() != Settings::getInstance()->getBool("UseCustomCollectionsSystem")) { Settings::getInstance()->setBool("UseCustomCollectionsSystem", use_custom_collections_system->getState()); if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); setNeedsSaving(); setNeedsSorting(); setNeedsSortingCollections(); setNeedsCollectionsUpdate(); setNeedsReloading(); setNeedsGoToSystem(SystemData::sSystemVector.front()); setInvalidateCachedBackground(); } }); // Show system names in collections. auto collection_show_system_info = std::make_shared(); collection_show_system_info->setState( Settings::getInstance()->getBool("CollectionShowSystemInfo")); addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", collection_show_system_info); addSaveFunc([this, collection_show_system_info] { if (collection_show_system_info->getState() != Settings::getInstance()->getBool("CollectionShowSystemInfo")) { Settings::getInstance()->setBool("CollectionShowSystemInfo", collection_show_system_info->getState()); setNeedsSaving(); setNeedsReloading(); setInvalidateCachedBackground(); } }); } void GuiCollectionSystemsOptions::createCustomCollection(std::string inName) { if (CollectionSystemsManager::getInstance()->isEditing()) CollectionSystemsManager::getInstance()->exitEditMode(); std::string collectionName { CollectionSystemsManager::getInstance()->getValidNewCollectionName(inName)}; SystemData* newCollection { CollectionSystemsManager::getInstance()->addNewCustomCollection(collectionName)}; CollectionSystemsManager::getInstance()->saveCustomCollection(newCollection); mCollectionSystemsCustom->add(collectionName, collectionName, true); mAddedCustomCollection = true; setNeedsGoToStart(); if (Settings::getInstance()->getBool("UseCustomCollectionsSystem")) setNeedsGoToGroupedCollections(); else setNeedsGoToSystem(newCollection); Window* window {mWindow}; while (window->peekGui() && window->peekGui() != ViewController::getInstance()) delete window->peekGui(); CollectionSystemsManager::getInstance()->setEditMode(collectionName); }