From 1a0b2f8bb7e175e8dd77d3a0559d6642538bbcf4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 21 Oct 2020 21:56:31 +0200 Subject: [PATCH] Multiple improvements to the handling of custom collections. Also improved the random game and random system functions. --- USERGUIDE.md | 25 ++---- es-app/src/CollectionSystemManager.cpp | 39 +++++++-- es-app/src/CollectionSystemManager.h | 2 +- es-app/src/FileData.cpp | 38 +++++++++ es-app/src/SystemData.cpp | 44 +++++----- es-app/src/SystemData.h | 1 - es-app/src/guis/GuiGamelistOptions.cpp | 82 +++++++++++-------- es-app/src/guis/GuiGamelistOptions.h | 2 + es-app/src/guis/GuiMenu.cpp | 10 --- .../views/gamelist/DetailedGameListView.cpp | 53 ++++++++++-- .../views/gamelist/ISimpleGameListView.cpp | 8 -- .../src/views/gamelist/VideoGameListView.cpp | 72 +++++++++++++--- themes/rbsimple-DE/MISSING.md | 1 - 13 files changed, 253 insertions(+), 124 deletions(-) diff --git a/USERGUIDE.md b/USERGUIDE.md index 63d0dcae2..e1b467974 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -202,27 +202,27 @@ The platform name for the Commodore 64 is **c64**, so the following structure wo ~/ROMs/c64/Multidisk ~/ROMs/c64/Multidisk/Last Ninja 2/LNINJA2A.D64 ~/ROMs/c64/Multidisk/Last Ninja 2/LNINJA2B.D64 -~/ROMs/c64/Multidisk/Last Ninja 2/Last Ninja 2 (playlist).m3u +~/ROMs/c64/Multidisk/Last Ninja 2/Last Ninja 2.m3u ~/ROMs/c64/Multidisk/Pirates/PIRAT-E0.d64 ~/ROMs/c64/Multidisk/Pirates/PIRAT-E1.d64 ~/ROMs/c64/Multidisk/Pirates/PIRAT-E2.d64 -~/ROMs/c64/Multidisk/Pirates/Pirates! (playlist).m3u +~/ROMs/c64/Multidisk/Pirates/Pirates!.m3u ``` It's highly recommended to create **.m3u** playlist files for multi-disk images as this simplifies the disk swapping in the emulator. It's then this .m3u file that should be selected for launching the game. -The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2 (playlist).m3u: +The .m3u file simply contains a list of the game files, for example in the case of Last Ninja 2.m3u: ``` LNINJA2A.D64 LNINJA2B.D64 ``` -It's recommended to not have the exact same filename for the .m3u file as for the directory as that will lead to a quite strange behavior where any game video that was playing when displaying the directory will continue to play when entering the directory (assuming the .m3u playlist is the first file that is selected). For some people this may be the desired behavior though, so the possibility is retained and it's not considered a bug. Putting the text within brackets will make the scraper skip this data so that automatic scraping still works correctly. +It's recommended to have the exact same filename for the .m3u file as for the directory as the game media files will then be shared between the two. This saves some unnecessary scraping as well as some disk space. It's of course also possible to skip this type of directory structure and put all the games in the root folder, but then there will be multiple entries for the same game which is not so tidy. Another approach would be to put all the files in the root folder and then hide the game files, showing only the .m3u playlist. But it's probably quite confusing to start a game that looks like a single-disk game and then be prompted for disk swaps by the emulator (even if the .m3u playlists automates disk swapping, it's still somehow confusing and I wouldn't recommend it). -When setting up games in this fashion, it's recommended to scrape the directory in addition to the .m3u file as it looks nicer to see images and metadata for the games also when browsing the folders. ES fully supports scraping folders, although some metadata is not included for folders for logical reasons. If you only scrape the folders and not the actual game files, it looks ok when browsing, but when a game is part of a collection, the metadata and images will be missing there. This includes the **Last played** and **All games** collections for instance. Also note that while it's possible to mark a folder as a favorite, it will never be part of a collection, such as **Favorites**. +When setting up games in this fashion, it's recommended to scrape the directory in addition to the .m3u file as it looks nicer to see the metadata for the games also when browsing the folders. ES fully supports scraping folders, although some metadata is not included for folders for logical reasons. If you only scrape the folders and not the actual game files, it looks ok when browsing, but when a game is part of a collection, the metadata will be missing there. This includes the **Last played** and **All games** collections for instance. Also note that while it's possible to mark a folder as a favorite, it will never be part of a collection, such as **Favorites**. As well it's recommended to set the flags **Exclude from game counter** and **Exclude from automatic scraper** for the actual game files so that they are not counted (the game counter is shown on the system view) and not scraped if running the multi-scraper. It's enough to scrape the .m3u playlist file and the game folder. But if you only intend to manually scrape file-per-file then you don't need to bother with this. For a cleaner look, it's also possible to set the flag **Hide metadata fields** for the game files. @@ -1018,9 +1018,9 @@ Note that you should only enable these collections if you really need them as th These are collections that you create yourself. Example of such collections could be grouping in genres like _Shoot em up_, _Fighting games_ etc. or perhaps a time period like '1980s', '1990s' and so on. -If the theme set supports it, you can create a custom collection directly from a theme. However, as of version 1.0.0, rbsimple-DE does not provide such themes. +If the theme set supports it, you can create a custom collection directly from a theme. However, bsimple-DE does not provide such themes as it's believed that grouping them together in a dedicated **Collections** system is a more elegant solution. Especially since the theme set would need to ship with an almost endless amount of collection themes for whatever categories the users would like to use for their game collections. -But if you have enabled the option **Group unthemed custom collections** (it's active by default), any collections you add will show up in the special 'Collection' system. Here you can access them just as you would access folders inside a regular game system. +So if you have enabled the option **Group unthemed custom collections** (it's enabled by default), any collections you add will show up in the special 'Collections' system. Here you can access them just as you would access folders inside a regular game system. The amount of games per collection is shown in the description, and the game media for a random game from the collection is displayed each time you browse through the collection list. To create a custom collection, go to 'Game collection settings' in the main menu and choose 'Create new custom collection'. @@ -1047,20 +1047,9 @@ 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 aafcfd7c7..6afc64aec 100644 --- a/es-app/src/CollectionSystemManager.cpp +++ b/es-app/src/CollectionSystemManager.cpp @@ -35,6 +35,7 @@ #include #include +#include std::string myCollectionsName = "collections"; @@ -272,11 +273,6 @@ 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); } } @@ -670,7 +666,6 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file) systemViewToUpdate->getIndex()->addToIndex(newGame); refreshCollectionSystems(newGame); } - updateCollectionFolderMetadata(sysData); saveCustomCollection(sysData); } else { @@ -737,15 +732,35 @@ void CollectionSystemManager::initAutoCollectionSystems() } // Used to generate a description of the collection, all other metadata fields are hidden. -void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) +FileData* CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) { FileData* rootFolder = sys->getRootFolder(); - rootFolder->metadata.set("hidemetadata", "true"); - std::string desc = "This collection is empty."; std::vector gamesList = rootFolder->getChildren(); + std::vector gamesListRandom; unsigned int gameCount = gamesList.size(); + // If there is more than 1 game in the collection, then randomize the example game names. + if (gameCount > 1) { + std::random_device randDev; + // Mersenne Twister pseudorandom number generator. + std::mt19937 engine{randDev()}; + unsigned int target; + + for (unsigned int i = 0; i < 3; i++) { + std::uniform_int_distribution uniform_dist(0, gameCount - 1 - i); + target = uniform_dist(engine); + gamesListRandom.push_back(gamesList[target]); + std::vector::iterator it = (gamesList.begin() + target); + gamesList.erase(it); + if (gamesList.size() == 0) + break; + } + gamesList.clear(); + gamesList.insert(gamesList.end(), gamesListRandom.begin(), gamesListRandom.end()); + gamesListRandom.clear(); + } + if (gameCount > 0) { switch (gameCount) { case 1: @@ -772,6 +787,12 @@ void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys) } rootFolder->metadata.set("desc", desc); + // Return a pointer to the first game so that its + // game media can be displayed in the gamelist. + if (gamesList.size() > 0) + return gamesList.front(); + else + return nullptr; } void CollectionSystemManager::initCustomCollectionSystems() diff --git a/es-app/src/CollectionSystemManager.h b/es-app/src/CollectionSystemManager.h index 7591cd717..36f959f52 100644 --- a/es-app/src/CollectionSystemManager.h +++ b/es-app/src/CollectionSystemManager.h @@ -91,7 +91,7 @@ public: bool toggleGameInCollection(FileData* file); SystemData* getSystemToView(SystemData* sys); - void updateCollectionFolderMetadata(SystemData* sys); + FileData* updateCollectionFolderMetadata(SystemData* sys); bool getIsCustomCollection(SystemData* system); diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 588d42447..796dc5fe2 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -447,6 +447,25 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending, std::vector mChildrenFolders; std::vector mChildrenOthers; + // For grouped custom collections, always sort the collection list as 'filename, ascending'. + // The individual collections are however sorted as any normal systems/folders. + if (mSystem->isCollection() && mSystem->getFullName() == "collections") { + std::stable_sort(mChildren.begin(), mChildren.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { + // Build mFirstLetterIndex. + const char firstChar = toupper((*it)->getSortName().front()); + mFirstLetterIndex.push_back(std::string(1, firstChar)); + if ((*it)->getChildren().size() > 0) + (*it)->sort(comparator, ascending, gameCount); + } + // Sort and make each entry unique in mFirstLetterIndex. + std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); + auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); + mFirstLetterIndex.erase(last, mFirstLetterIndex.end()); + return; + } + if (foldersOnTop) { for (unsigned int i = 0; i < mChildren.size(); i++) { if (mChildren[i]->getType() == FOLDER) { @@ -545,6 +564,25 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop"); bool hasFolders = false; + // For grouped custom collections, always sort the collection list as 'filename, ascending'. + // The individual collections are however sorted as any normal systems/folders. + if (mSystem->isCollection() && mSystem->getFullName() == "collections") { + std::stable_sort(mChildren.begin(), mChildren.end(), + getSortTypeFromString("filename, ascending").comparisonFunction); + for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { + // Build mFirstLetterIndex. + const char firstChar = toupper((*it)->getSortName().front()); + mFirstLetterIndex.push_back(std::string(1, firstChar)); + if ((*it)->getChildren().size() > 0) + (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); + } + // Sort and make each entry unique in mFirstLetterIndex. + std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); + auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end()); + mFirstLetterIndex.erase(last, mFirstLetterIndex.end()); + return; + } + for (unsigned int i = 0; i < mChildren.size(); i++) { // Game count, which will be displayed in the system view. if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) { diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 1a963d198..b7f667f8f 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -27,6 +27,7 @@ #include #include +#include std::vector SystemData::sSystemVector; @@ -528,7 +529,11 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem) do { // Get a random number in range. - int target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1)); + std::random_device randDev; + // Mersenne Twister pseudorandom number generator. + std::mt19937 engine{randDev()}; + std::uniform_int_distribution uniform_dist(0, total - 1); + int target = uniform_dist(engine); for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++) { if ((*it)->isGameSystem()) { @@ -547,33 +552,11 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem) return randomSystem; } -FileData* SystemData::getRandomCollectionFolder(const FileData* currentFolder) -{ - if (!currentFolder) - return nullptr; - - std::vector collectionFolders = currentFolder->getParent()->getChildren(); - - unsigned int total = collectionFolders.size(); - int target = 0; - - if (total < 2) - return nullptr; - - do { - // Get a random number in range. - target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1)); - } - while (collectionFolders.at(target) == currentFolder); - - return collectionFolders.at(target); -} - FileData* SystemData::getRandomGame(const FileData* currentGame) { std::vector gameList = mRootFolder->getFilesRecursive(GAME, true); - if (gameList.size() == 1) + if (!currentGame && gameList.size() == 1) return gameList.front(); if (currentGame && currentGame->getType() == PLACEHOLDER) @@ -628,7 +611,11 @@ FileData* SystemData::getRandomGame(const FileData* currentGame) do { // Get a random number in range. - target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1)); + std::random_device randDev; + // Mersenne Twister pseudorandom number generator. + std::mt19937 engine{randDev()}; + std::uniform_int_distribution uniform_dist(0, total - 1); + target = uniform_dist(engine); } while (currentGame && gameList.at(target) == currentGame); @@ -648,6 +635,13 @@ void SystemData::sortSystem(bool reloadGamelist) favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); FileData* rootFolder = getRootFolder(); + // Assign the sort type to all grouped custom collections. + if (mIsCollectionSystem && mFullName == "collections") { + for (auto it = rootFolder->getChildren().begin(); + it != rootFolder->getChildren().end(); it++) { + setupSystemSortType((*it)->getSystem()->getRootFolder()); + } + } setupSystemSortType(rootFolder); rootFolder->sort(rootFolder->getSortTypeFromString( diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index d729eb63a..a2250c1a2 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -90,7 +90,6 @@ public: SystemData* getNext() const; SystemData* getPrev() const; static SystemData* getRandomSystem(const SystemData* currentSystem); - static FileData* getRandomCollectionFolder(const FileData* currentFolder); FileData* getRandomGame(const FileData* currentGame = nullptr); void sortSystem(bool reloadGamelist = true); diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 51f9330df..47825f90e 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -32,7 +32,9 @@ GuiGamelistOptions::GuiGamelistOptions( mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false), - mCancelled(false) + mCancelled(false), + isCustomCollection(false), + isCustomCollectionGroup(false) { addChild(&mMenu); @@ -43,6 +45,14 @@ GuiGamelistOptions::GuiGamelistOptions( fromPlaceholder = file->isPlaceHolder(); ComponentListRow row; + // There is some special logic required for custom collections. + if (file->getSystem()->isCustomCollection() && + file->getPath() != file->getSystem()->getName()) + isCustomCollection = true; + else if (file->getSystem()->isCustomCollection() && + file->getPath() == file->getSystem()->getName()) + isCustomCollectionGroup = true; + // Read the setting for whether folders are sorted on top of the gamelists. // Also check if the gamelist only contains folders, as generated by the FileData sorting. mFoldersOnTop = Settings::getInstance()->getBool("FoldersOnTop"); @@ -96,31 +106,42 @@ GuiGamelistOptions::GuiGamelistOptions( if (system->getName() != "recent") mMenu.addWithLabel("JUMP TO..", mJumpToLetterList); - // Sort list by selected sort type (persistent throughout the program session). - mListSort = std::make_shared(mWindow, getHelpStyle(), "SORT GAMES BY", false); - FileData* root = mSystem->getRootFolder(); - std::string sortType = root->getSortTypeString(); - - for (unsigned int i = 0; i add(sort.description, &sort, 1); + // Add the sorting entry, unless this is the grouped custom collections list. + if (!isCustomCollectionGroup) { + // Sort list by selected sort type (persistent throughout the program session). + mListSort = std::make_shared(mWindow, getHelpStyle(), "SORT GAMES BY", false); + FileData* root; + if (isCustomCollection) + root = getGamelist()->getCursor()->getSystem()->getRootFolder(); else - mListSort->add(sort.description, &sort, 0); + root = mSystem->getRootFolder(); + + std::string sortType = root->getSortTypeString(); + + for (unsigned int i = 0; i add(sort.description, &sort, 1); + else + mListSort->add(sort.description, &sort, 0); + } + // Don't show the sort type option if the gamelist type is recent/last played. + if (system->getName() != "recent") + mMenu.addWithLabel("SORT GAMES BY", mListSort); } - // Don't show the sort type option if the gamelist type is recent/last played. - if (system->getName() != "recent") - mMenu.addWithLabel("SORT GAMES BY", mListSort); } - // Show filtered menu. - if (system->getName() != "recent" && !Settings::getInstance()->getBool("ForceDisableFilters")) { - row.elements.clear(); - row.addElement(std::make_shared - (mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); - row.addElement(makeArrow(mWindow), false); - row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); - mMenu.addRow(row); + // Add the filters entry, unless this is the grouped custom collections list. + if (!isCustomCollectionGroup) { + if (system->getName() != "recent" && + !Settings::getInstance()->getBool("ForceDisableFilters")) { + row.elements.clear(); + row.addElement(std::make_shared + (mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); + row.addElement(makeArrow(mWindow), false); + row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this)); + mMenu.addRow(row); + } } std::map customCollections = @@ -194,24 +215,21 @@ GuiGamelistOptions::~GuiGamelistOptions() return; if (!fromPlaceholder) { - FileData* root = mSystem->getRootFolder(); + FileData* root; + if (isCustomCollection) + root = getGamelist()->getCursor()->getSystem()->getRootFolder(); + else + root = mSystem->getRootFolder(); // If a new sorting type was selected, then sort and update mSortTypeString for the system. - if ((*mListSort->getSelected()).description != root->getSortTypeString()) { + if (!isCustomCollectionGroup && + (*mListSort->getSelected()).description != root->getSortTypeString()) { // This will also recursively sort children. root->sort(*mListSort->getSelected(), mFavoritesSorting); root->setSortTypeString((*mListSort->getSelected()).description); // 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? diff --git a/es-app/src/guis/GuiGamelistOptions.h b/es-app/src/guis/GuiGamelistOptions.h index f9ed7d5ed..df8d78346 100644 --- a/es-app/src/guis/GuiGamelistOptions.h +++ b/es-app/src/guis/GuiGamelistOptions.h @@ -57,6 +57,8 @@ private: bool fromPlaceholder; bool mFiltersChanged; bool mCancelled; + bool isCustomCollection; + bool isCustomCollectionGroup; std::vector mFirstLetterIndex; std::string mCurrentFirstCharacter; std::string FAVORITE_CHAR; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 27e61b42f..8c88c43b6 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -375,16 +375,6 @@ 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()); diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index dc17d5245..17aca8c77 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -10,6 +10,8 @@ #include "animations/LambdaAnimation.h" #include "views/ViewController.h" +#include "CollectionSystemManager.h" +#include "SystemData.h" #define FADE_IN_START_OPACITY 0.5f #define FADE_IN_TIME 300 @@ -240,15 +242,30 @@ void DetailedGameListView::updateInfoPanel() FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected(); // If the game data has already been rendered to the info panel, then skip it this time. - if (file == mLastUpdated) { + if (file == mLastUpdated) return; - } - mLastUpdated = file; + + if (!mList.isScrolling()) + mLastUpdated = file; bool hideMetaDataFields = false; - if (file) - hideMetaDataFields = (file->metadata.get("hidemetadata") == "true"); + if (file) { + // Always hide the metadata fields if browsing grouped custom collections. + if (file->getSystem()->isCustomCollection() && + file->getPath() == file->getSystem()->getName()) + hideMetaDataFields = true; + else + hideMetaDataFields = (file->metadata.get("hidemetadata") == "true"); + } + + // If we're scrolling, hide the metadata fields if the last game had this options set, + // or if we're in the grouped custom collection view. + if (mList.isScrolling()) + if (mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true" || + (mLastUpdated->getSystem()->isCustomCollection() && + mLastUpdated->getPath() == mLastUpdated->getSystem()->getName())) + hideMetaDataFields = true; if (hideMetaDataFields) { mLblRating.setVisible(false); @@ -292,9 +309,29 @@ void DetailedGameListView::updateInfoPanel() fadingOut = true; } else { - mThumbnail.setImage(file->getThumbnailPath()); - mMarquee.setImage(file->getMarqueePath()); - mImage.setImage(file->getImagePath()); + // If we're browsing a grouped custom collection, then update the folder metadata + // which will generate a description of three random games and return a pointer to + // the first of these so that we can display its game media. + if (file->getSystem()->isCustomCollection() && + file->getPath() == file->getSystem()->getName()) { + FileData* randomGame = CollectionSystemManager::get()-> + updateCollectionFolderMetadata(file->getSystem()); + if (randomGame) { + mThumbnail.setImage(randomGame->getThumbnailPath()); + mMarquee.setImage(randomGame->getMarqueePath()); + mImage.setImage(randomGame->getImagePath()); + } + else { + mThumbnail.setImage(""); + mMarquee.setImage(""); + mImage.setImage(""); + } + } + else { + mThumbnail.setImage(file->getThumbnailPath()); + mMarquee.setImage(file->getMarqueePath()); + mImage.setImage(file->getImagePath()); + } // Fade in the game image. auto func = [this](float t) { diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index fc5318714..d4adc79d9 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -166,14 +166,6 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor()); if (randomGame) setCursor(randomGame); - // If it's not a game, maybe it's a folder for an unthemed collection. - else if (getCursor()->getSystem()->isCollection()) { - FileData* randomFolder = - mRoot->getSystem()->getRandomCollectionFolder(getCursor()); - if (randomFolder) - setCursor(randomFolder); - } - return true; } } diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index b21a99188..bc60be638 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -18,6 +18,8 @@ #if defined(_RPI_) #include "Settings.h" #endif +#include "CollectionSystemManager.h" +#include "SystemData.h" #define FADE_IN_START_OPACITY 0.5f #define FADE_IN_TIME 650 @@ -52,7 +54,8 @@ VideoGameListView::VideoGameListView( mPlayers(window), mLastPlayed(window), mPlayCount(window), - mName(window) + mName(window), + mLastUpdated(nullptr) { const float padding = 0.01f; @@ -264,15 +267,30 @@ void VideoGameListView::updateInfoPanel() FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected(); // If the game data has already been rendered to the info panel, then skip it this time. - if (file == mLastUpdated) { + if (file == mLastUpdated) return; - } - mLastUpdated = file; + + if (!mList.isScrolling()) + mLastUpdated = file; bool hideMetaDataFields = false; - if (file) - hideMetaDataFields = (file->metadata.get("hidemetadata") == "true"); + if (file) { + // Always hide the metadata fields if browsing grouped custom collections. + if (file->getSystem()->isCustomCollection() && + file->getPath() == file->getSystem()->getName()) + hideMetaDataFields = true; + else + hideMetaDataFields = (file->metadata.get("hidemetadata") == "true"); + } + + // If we're scrolling, hide the metadata fields if the last game had this options set, + // or if we're in the grouped custom collection view. + if (mList.isScrolling()) + if (mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true" || + (mLastUpdated->getSystem()->isCustomCollection() && + mLastUpdated->getPath() == mLastUpdated->getSystem()->getName())) + hideMetaDataFields = true; if (hideMetaDataFields) { mLblRating.setVisible(false); @@ -317,12 +335,44 @@ void VideoGameListView::updateInfoPanel() fadingOut = true; } else { - mThumbnail.setImage(file->getThumbnailPath()); - mMarquee.setImage(file->getMarqueePath()); - mVideo->setImage(file->getImagePath()); + // If we're browsing a grouped custom collection, then update the folder metadata + // which will generate a description of three random games and return a pointer to + // the first of these so that we can display its game media. + if (file->getSystem()->isCustomCollection() && + file->getPath() == file->getSystem()->getName()) { + FileData* randomGame = CollectionSystemManager::get()-> + updateCollectionFolderMetadata(file->getSystem()); + if (randomGame) { + mThumbnail.setImage(randomGame->getThumbnailPath()); + mMarquee.setImage(randomGame->getMarqueePath()); + mVideo->setImage(randomGame->getImagePath()); + // Always stop the video before setting a new video as it will otherwise continue + // to play if it has the same path (i.e. it is the same physical video file) as + // the previously set video. + // That may happen when entering a folder with the same name as the first game + // file inside, or as in this case, when entering a custom collection. + mVideo->onHide(); - if (!mVideo->setVideo(file->getVideoPath())) - mVideo->setDefaultVideo(); + if (!mVideo->setVideo(randomGame->getVideoPath())) + mVideo->setDefaultVideo(); + } + else { + mThumbnail.setImage(""); + mMarquee.setImage(""); + mVideo->setImage(""); + mVideo->setVideo(""); + mVideo->setDefaultVideo(); + } + } + else { + mThumbnail.setImage(file->getThumbnailPath()); + mMarquee.setImage(file->getMarqueePath()); + mVideo->setImage(file->getImagePath()); + mVideo->onHide(); + + if (!mVideo->setVideo(file->getVideoPath())) + mVideo->setDefaultVideo(); + } mVideoPlaying = true; diff --git a/themes/rbsimple-DE/MISSING.md b/themes/rbsimple-DE/MISSING.md index 9f2b4f611..e7639b7df 100644 --- a/themes/rbsimple-DE/MISSING.md +++ b/themes/rbsimple-DE/MISSING.md @@ -1,6 +1,5 @@ Missing systems =============== -* Themes for custom collections such as "Fighting games", "Driving games", "Shoot 'em up" etc. Missing (or not updated) theme data