diff --git a/USERGUIDE.md b/USERGUIDE.md index d9ecab1bb..4f190aa86 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -1047,9 +1047,20 @@ The file contents is simply a list of ROM files, such as the following: Any changes to custom collections (for example adding or removing a game) will be immediately written to the corresponding collection configuration file. +>>> Note that if you for example copy or migrate a collection from a previous version of EmulationStation or if you're setting up EmulationStation Desktop Edition on a new computer, even though you copy the files into the collections directory, they will not show up in the application. You always need to enable the collection in the menu. ES looks inside the es_settings.cfg file during startup to see which collections should be shown. If you're migrating from a previous version of EmulationStation that has absolute paths in the collection files, these will be rewritten with the %ROMPATH% variable the first time you make a change to the collection. +>>> + +It's also possible to add media files to the custom collections entries if they are grouped under the _collections_ system (this is enabled by default). Simply create a directory under your media folder, for example `~/.emulationstation/downloaded_media`, which corresponds to the collection name. For our example it would be _1980s_. The media files themselves should also be named after the collection. This is an example of what this could look like: + +``` +~/.emulationstation/downloaded_media/1980s/covers/1980s.png +~/.emulationstation/downloaded_media/1980s/videos/1980s.mp4 +``` + +For more details on how to manually copy media files, see the section [Manually copying game media files](USERGUIDE.md#manually-copying-game-media-files) earlier in this guide. ## Themes diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp index 0fe8a257c..aafcfd7c7 100644 --- a/es-app/src/CollectionSystemManager.cpp +++ b/es-app/src/CollectionSystemManager.cpp @@ -272,6 +272,11 @@ void CollectionSystemManager::updateSystemsList() if (rootFolder->getChildren().size() > 0) { rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder-> getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom")); + // Update the custom collections metadata now that the sorting has been done. + std::vector customCollections = rootFolder->getChildren(); + for (auto it = customCollections.cbegin(); it != customCollections.cend(); it++) + updateCollectionFolderMetadata((*it)->getSystem()); + SystemData::sSystemVector.push_back(mCustomCollectionsBundle); } } @@ -588,17 +593,17 @@ void CollectionSystemManager::setEditMode(std::string collectionName) // If it's bundled, this needs to be the bundle system. mEditingCollectionSystemData = sysData; - GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Editing the '" + + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "EDITING THE '" + Utils::String::toUpper(collectionName) + - "' Collection. Add/remove games with Y.", 10000); + "' COLLECTION, ADD/REMOVE GAMES WITH 'Y'", 10000); mWindow->setInfoPopup(s); } void CollectionSystemManager::exitEditMode() { - GuiInfoPopup* s = new GuiInfoPopup(mWindow, "Finished editing the '" + - mEditingCollection + "' Collection.", 4000); + GuiInfoPopup* s = new GuiInfoPopup(mWindow, "FINISHED EDITING THE '" + + Utils::String::toUpper(mEditingCollection) + "' COLLECTION", 4000); mWindow->setInfoPopup(s); mIsEditingCustom = false; @@ -685,11 +690,13 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file) refreshCollectionSystems(file->getSourceFileData()); } if (adding) - s = new GuiInfoPopup(mWindow, "Added '" + Utils::String::removeParenthesis(name) + - "' to '" + Utils::String::toUpper(sysName) + "'", 4000); + s = new GuiInfoPopup(mWindow, "ADDED '" + + Utils::String::toUpper(Utils::String::removeParenthesis(name)) + + "' TO '" + Utils::String::toUpper(sysName) + "'", 4000); else - s = new GuiInfoPopup(mWindow, "Removed '" + Utils::String::removeParenthesis(name) + - "' from '" + Utils::String::toUpper(sysName) + "'", 4000); + s = new GuiInfoPopup(mWindow, "REMOVED '" + + Utils::String::toUpper(Utils::String::removeParenthesis(name)) + + "' FROM '" + Utils::String::toUpper(sysName) + "'", 4000); mWindow->setInfoPopup(s); return true; } @@ -729,87 +736,42 @@ void CollectionSystemManager::initAutoCollectionSystems() } } -// This may come in handy if at any point in time in the future we want to -// automatically generate metadata for a folder. +// Used to generate a description of the collection, all other metadata fields are hidden. void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) { FileData* rootFolder = sys->getRootFolder(); + rootFolder->metadata.set("hidemetadata", "true"); std::string desc = "This collection is empty."; - std::string rating = "0"; - std::string players = "1"; - std::string releasedate = "N/A"; - std::string developer = "None"; - std::string genre = "None"; - std::string video = ""; - std::string thumbnail = ""; - std::string image = ""; + std::vector gamesList = rootFolder->getChildren(); + unsigned int gameCount = gamesList.size(); - std::unordered_map games = rootFolder->getChildrenByFilename(); - - if (games.size() > 0) { - std::string games_list = ""; - int games_counter = 0; - for (std::unordered_map::const_iterator - iter = games.cbegin(); iter != games.cend(); ++iter) { - - games_counter++; - FileData* file = iter->second; - - std::string new_rating = file->metadata.get("rating"); - std::string new_releasedate = file->metadata.get("releasedate"); - std::string new_developer = file->metadata.get("developer"); - std::string new_genre = file->metadata.get("genre"); - std::string new_players = file->metadata.get("players"); - - rating = (new_rating > rating ? (new_rating != "" ? - new_rating : rating) : rating); - - players = (new_players > players ? (new_players != "" ? - new_players : players) : players); - - releasedate = (new_releasedate < releasedate ? (new_releasedate != "" ? - new_releasedate : releasedate) : releasedate); - - developer = (developer == "None" ? new_developer : (new_developer != developer ? - "Various" : new_developer)); - - genre = (genre == "None" ? new_genre : (new_genre != genre ? - "Various" : new_genre)); - - switch (games_counter) { - case 2: - case 3: - games_list += ", "; - case 1: - games_list += "'" + file->getName() + "'"; - break; - case 4: - games_list += " among other titles."; - } - } - - desc = "This collection contains " + std::to_string(games_counter) + - " games, including " + games_list; - - FileData* randomGame = sys->getRandomGame(); - - if (randomGame) { - video = randomGame->getVideoPath(); - thumbnail = randomGame->getThumbnailPath(); - image = randomGame->getImagePath(); + if (gameCount > 0) { + switch (gameCount) { + case 1: + desc = "This collection contains 1 game: '" + gamesList[0]->metadata.get("name") + + " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + "]'."; + break; + case 2: + desc = "This collection contains 2 games: '" + gamesList[0]->metadata.get("name") + + " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + + "]' and '" + gamesList[1]->metadata.get("name") + " [" + + gamesList[1]->getSourceFileData()->getSystem()->getName() + "]'."; + break; + default: + desc = "This collection contains " + std::to_string(gameCount) + + " games: '" + gamesList[0]->metadata.get("name") + + " [" + gamesList[0]->getSourceFileData()->getSystem()->getName() + + "]', '" + gamesList[1]->metadata.get("name") + " [" + + gamesList[1]->getSourceFileData()->getSystem()->getName() + "]' and '" + + gamesList[2]->metadata.get("name") + " [" + + gamesList[2]->getSourceFileData()->getSystem()->getName() + "]'"; + desc += (gameCount == 3 ? "." : ", among others."); + break; } } rootFolder->metadata.set("desc", desc); - rootFolder->metadata.set("rating", rating); - rootFolder->metadata.set("players", players); - rootFolder->metadata.set("genre", genre); - rootFolder->metadata.set("releasedate", releasedate); - rootFolder->metadata.set("developer", developer); - rootFolder->metadata.set("video", video); - rootFolder->metadata.set("thumbnail", thumbnail); - rootFolder->metadata.set("image", image); } void CollectionSystemManager::initCustomCollectionSystems() @@ -962,11 +924,10 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys index->addToIndex(newGame); } else { - LOG(LogWarning) << "File \"" << gameKey << "\" does not exist, ignoring entry"; + LOG(LogWarning) << "File \"" << gameKey << + "\" does not exist, is hidden, or is not counted as a game, ignoring entry"; } } - - updateCollectionFolderMetadata(newSys); } // Functions below to handle System View removal and insertion of collections. diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index fe32ce956..c429c54d7 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -132,6 +132,8 @@ void GuiCollectionSystemsOptions::addEntry(const char* name, unsigned int color, } void GuiCollectionSystemsOptions::createCollection(std::string inName) { + if (CollectionSystemManager::get()->isEditing()) + CollectionSystemManager::get()->exitEditMode(); std::string name = CollectionSystemManager::get()->getValidNewCollectionName(inName); SystemData* newSys = CollectionSystemManager::get()->addNewCustomCollection(name); CollectionSystemManager::get()->saveCustomCollection(newSys); diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index afcc29690..51f9330df 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -204,6 +204,14 @@ GuiGamelistOptions::~GuiGamelistOptions() // Notify that the root folder was sorted (refresh). getGamelist()->onFileChanged(root, FILE_SORTED); + if (mSystem->isCollection() && mSystem->getFullName() == "collections") { + // Update the custom collections metadata now that we have changed the sorting. + std::vector customCollections = root->getChildren(); + for (auto it = customCollections.cbegin(); it != customCollections.cend(); it++) + CollectionSystemManager::get()-> + updateCollectionFolderMetadata((*it)->getSystem()); + ViewController::get()->reloadGameListView(mSystem); + } } // Has the user changed the letter using the quick selector? @@ -270,12 +278,12 @@ void GuiGamelistOptions::openMetaDataEd() clearGameBtnFunc = [this, file] { if (file->getType() == FOLDER) { - LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder '" << - file->getFullPath() << "'"; + LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the folder \"" << + file->getFullPath() << "\""; } else { - LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file '" << - file->getFullPath() << "'"; + LOG(LogInfo) << "Deleting the media files and gamelist.xml entry for the file \"" << + file->getFullPath() << "\""; } ViewController::get()->getGameListView(file->getSystem()).get()->removeMedia(file); @@ -298,8 +306,8 @@ void GuiGamelistOptions::openMetaDataEd() }; deleteGameBtnFunc = [this, file] { - LOG(LogInfo) << "Deleting the game file '" << file->getFullPath() << - "', all its media files and its gamelist.xml entry."; + LOG(LogInfo) << "Deleting the game file \"" << file->getFullPath() << + "\", all its media files and its gamelist.xml entry."; CollectionSystemManager::get()->deleteCollectionFiles(file); ViewController::get()->getGameListView( file->getSystem()).get()->removeMedia(file); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 169d82d78..da9ac1005 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -362,7 +362,7 @@ void GuiMenu::openUISettings() defaultSortOrder->add(sort.description, &sort, false); } s->addWithLabel("DEFAULT SORT ORDER", defaultSortOrder); - s->addSaveFunc([defaultSortOrder, sortOrder] { + s->addSaveFunc([defaultSortOrder, sortOrder, this] { std::string selectedSortOrder = defaultSortOrder.get()->getSelected()->description; if (selectedSortOrder != sortOrder) { Settings::getInstance()->setString("DefaultSortOrder", selectedSortOrder); @@ -374,11 +374,21 @@ void GuiMenu::openUISettings() (*it)->sortSystem(); + // Update the metadata for any custom collections. + if ((*it)->isCollection() && (*it)->getFullName() == "collections") { + std::vector customCollections = + (*it)->getRootFolder()->getChildren(); + for (auto it2 = customCollections.cbegin(); + it2 != customCollections.cend(); it2++) + CollectionSystemManager::get()-> + updateCollectionFolderMetadata((*it2)->getSystem()); + } + // Jump to the first row of the gamelist. IGameListView* gameList = ViewController::get()->getGameListView((*it)).get(); gameList->setCursor(gameList->getFirstEntry()); - } + mWindow->invalidateCachedBackground(); } }); diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index eba7e7c92..fc5318714 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -179,7 +179,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) } else if (config->isMappedTo("y", input) && !UIModeController::getInstance()->isUIModeKid()) { - if (mRoot->getSystem()->isGameSystem()) { + if (mRoot->getSystem()->isGameSystem() && + getCursor()->getParent()->getPath() != "collections") { if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER) NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); // When marking or unmarking a game as favorite, don't jump to the new position @@ -201,7 +202,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); if (favoritesSorting && static_cast( - mRoot->getSystem()->getName()) != "recent") { + mRoot->getSystem()->getName()) != "recent" && + !CollectionSystemManager::get()->isEditing()) { FileData* entryToSelect; // Add favorite flag. if (!getCursor()->getFavorite()) { @@ -260,23 +262,29 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) setCursor(entryToSelect); } - // Marking folders as favorites is only cosmetic as they're not sorted - // differently and they're not part of any collections. So it makes more - // sense to do it here than to add the function to CollectionSystemManager. + // Marking folders as favorites don't make them part of any collections, + // so it makes more sense to handle it here than to add the function to + // CollectionSystemManager. if (entryToUpdate->getType() == FOLDER) { GuiInfoPopup* s; - MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata; - if (md->get("favorite") == "false") { - md->set("favorite", "true"); - s = new GuiInfoPopup(mWindow, "Marked folder '" + - Utils::String::removeParenthesis(entryToUpdate->getName()) + - "' as favorite", 4000); + if (CollectionSystemManager::get()->isEditing()) { + s = new GuiInfoPopup(mWindow, + "CAN'T ADD FOLDERS TO CUSTOM COLLECTIONS", 4000); } else { - md->set("favorite", "false"); - s = new GuiInfoPopup(mWindow, "Removed favorite marking for folder '" + - Utils::String::removeParenthesis(entryToUpdate->getName()) + - "'", 4000); + MetaDataList* md = &entryToUpdate->getSourceFileData()->metadata; + if (md->get("favorite") == "false") { + md->set("favorite", "true"); + s = new GuiInfoPopup(mWindow, "MARKED FOLDER '" + + Utils::String::toUpper(Utils::String::removeParenthesis( + entryToUpdate->getName())) + "' AS FAVORITE", 4000); + } + else { + md->set("favorite", "false"); + s = new GuiInfoPopup(mWindow, "REMOVED FAVORITE MARKING FOR FOLDER '" + + Utils::String::toUpper(Utils::String::removeParenthesis( + entryToUpdate->getName())) + "'", 4000); + } } mWindow->setInfoPopup(s); @@ -299,6 +307,13 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) return true; } + else if (CollectionSystemManager::get()->isEditing() && + entryToUpdate->metadata.get("nogamecount") == "true") { + GuiInfoPopup* s = new GuiInfoPopup(mWindow, + "CAN'T ADD ENTRIES THAT ARE NOT COUNTED " + "AS GAMES TO CUSTOM COLLECTIONS", 4000); + mWindow->setInfoPopup(s); + } else if (CollectionSystemManager::get()->toggleGameInCollection(entryToUpdate)) { // Jump to the first entry in the gamelist if the last favorite was unmarked. if (foldersOnTop && removedLastFavorite && diff --git a/themes/rbsimple-DE/custom-collections/systeminfo.xml b/themes/rbsimple-DE/custom-collections/systeminfo.xml index 0133722fe..d4ea87284 100644 --- a/themes/rbsimple-DE/custom-collections/systeminfo.xml +++ b/themes/rbsimple-DE/custom-collections/systeminfo.xml @@ -3,7 +3,7 @@ - Add your own custom collections here. + Find your own custom collections here.