diff --git a/NEWS.md b/NEWS.md index 6689e29b4..bec957836 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,7 +23,9 @@ Many bugs have been fixed, and numerous features that were only partially implem * Updated scraper to support additional media files, detailed configuration of what to scrape, semi-automatic mode etc. * In the metadata editor, any values updated by the single-game scraper or by the user are now highlighted using a different font color * Files or folders can now be flagged for exclusion when scraping with the multi-scraper, and for folders it can be set to apply recursively -* Gamelist sorting now working as expected and is persistent throughout the application session +* Gamelist sorting is now working as expected and is persistent throughout the application session +* Game counting is now done during sorting instead of every time a system is selected. This should make the UI more responsive in case of large game libraries +* Added a system view counter for favorite games in addition to the total number of games * Added support for jumping to the start and end of gamelists and menus using the controller trigger buttons (or equivalent keyboard mappings) * Full navigation sound support, configurable per theme with a fallback to the built-in sounds if the theme does not support it * New default theme rbsimple-DE bundled with the software, this theme is largely based on recalbox-multi by the Recalbox community diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp index dc7a55693..34f4c668f 100644 --- a/es-app/src/CollectionSystemManager.cpp +++ b/es-app/src/CollectionSystemManager.cpp @@ -1,4 +1,6 @@ +// SPDX-License-Identifier: MIT // +// EmulationStation Desktop Edition // CollectionSystemManager.cpp // // Manages collections of the following two types: @@ -30,8 +32,9 @@ #include "Settings.h" #include "SystemData.h" #include "ThemeData.h" -#include + #include +#include std::string myCollectionsName = "collections"; @@ -237,8 +240,7 @@ void CollectionSystemManager::updateSystemsList() } // If we were editing a custom collection, and it's no longer enabled, exit edit mode. - if (mIsEditingCustom && !mEditingCollectionSystemData->isEnabled) - { + if (mIsEditingCustom && !mEditingCollectionSystemData->isEnabled) { exitEditMode(); } } @@ -385,11 +387,15 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS if (sysData.decl.isCustom && Settings::getInstance()->getBool("UseCustomCollectionsSystem")) { // In case of a returned null pointer, we know there is no parent. - if (rootFolder->getParent() == nullptr) + if (rootFolder->getParent() == nullptr) { ViewController::get()->onFileChanged(rootFolder, FILE_METADATA_CHANGED); - else + } + else { + rootFolder->getParent()->sort(rootFolder->getSortTypeFromString( + rootFolder->getSortTypeString()), mFavoritesSorting); ViewController::get()->onFileChanged( rootFolder->getParent(), FILE_METADATA_CHANGED); + } } } } @@ -591,6 +597,9 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file) ViewController::get()->getGameListView(systemViewToUpdate).get()-> remove(collectionEntry, false); + systemViewToUpdate->getRootFolder()->sort(rootFolder->getSortTypeFromString( + rootFolder->getSortTypeString()), + Settings::getInstance()->getBool("FavFirstCustom")); } else { // We didn't find it here, so we should add it. @@ -601,10 +610,6 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file) onFileChanged(newGame, FILE_METADATA_CHANGED); if (name == "recent") rootFolder->sort(rootFolder->getSortTypeFromString("last played, descending")); - else - rootFolder->sort(rootFolder->getSortTypeFromString( - rootFolder->getSortTypeString()), - Settings::getInstance()->getBool("FavFirstCustom")); ViewController::get()->onFileChanged(systemViewToUpdate-> getRootFolder(), FILE_SORTED); @@ -612,6 +617,7 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file) // Add to bundle index as well, if needed. if (systemViewToUpdate != sysData) systemViewToUpdate->getIndex()->addToIndex(newGame); + refreshCollectionSystems(newGame); } updateCollectionFolderMetadata(sysData); } @@ -783,15 +789,15 @@ SystemData* CollectionSystemManager::addNewCustomCollection(std::string name) decl.name = name; decl.longName = name; - return createNewCollectionEntry(name, decl); + return createNewCollectionEntry(name, decl, true, true); } // Create a new empty collection system based on the name and declaration. SystemData* CollectionSystemManager::createNewCollectionEntry( - std::string name, CollectionSystemDecl sysDecl, bool index) + std::string name, CollectionSystemDecl sysDecl, bool index, bool custom) { SystemData* newSys = new SystemData( - name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true); + name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true, custom); CollectionSystemData newCollectionData; newCollectionData.system = newSys; diff --git a/es-app/src/CollectionSystemManager.h b/es-app/src/CollectionSystemManager.h index 66f9ba98c..79dd52b5b 100644 --- a/es-app/src/CollectionSystemManager.h +++ b/es-app/src/CollectionSystemManager.h @@ -1,4 +1,6 @@ +// SPDX-License-Identifier: MIT // +// EmulationStation Desktop Edition // CollectionSystemManager.h // // Manages collections of the following two types: @@ -17,7 +19,6 @@ // the required re-sort and refresh of the gamelists. // -#pragma once #ifndef ES_APP_COLLECTION_SYSTEM_MANAGER_H #define ES_APP_COLLECTION_SYSTEM_MANAGER_H @@ -111,7 +112,7 @@ private: void initCustomCollectionSystems(); SystemData* getAllGamesCollection(); SystemData* createNewCollectionEntry(std::string name, - CollectionSystemDecl sysDecl, bool index = true); + CollectionSystemDecl sysDecl, bool index = true, bool custom = false); void populateAutoCollection(CollectionSystemData* sysData); void populateCustomCollection(CollectionSystemData* sysData); @@ -126,9 +127,7 @@ private: std::vector getUserCollectionThemeFolders(); void trimCollectionCount(FileData* rootFolder, int limit); - bool themeFolderExists(std::string folder); - bool includeFileInAutoCollections(FileData* file); SystemData* mCustomCollectionsBundle; diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 69d4790c0..29ac644b7 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -436,7 +436,8 @@ void FileData::removeChild(FileData* file) assert(false); } -void FileData::sort(ComparisonFunction& comparator, bool ascending) +void FileData::sort(ComparisonFunction& comparator, bool ascending, + std::pair& gameCount) { mFirstLetterIndex.clear(); mOnlyFolders = true; @@ -507,6 +508,14 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending) } for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { + // Game count, which will be displayed in the system view. + if ((*it)->getType() == GAME && (*it)->getCountAsGame()) { + if (!(*it)->getFavorite()) + gameCount.first++; + else + gameCount.second++; + } + if ((*it)->getType() != FOLDER) mOnlyFolders = false; @@ -517,7 +526,7 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending) } // Iterate through any child folders. if ((*it)->getChildren().size() > 0) - (*it)->sort(comparator, ascending); + (*it)->sort(comparator, ascending, gameCount); } // If there are only folders in the gamelist, then it makes sense to still @@ -540,7 +549,8 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending) mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FOLDER_CHAR); } -void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending) +void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending, + std::pair& gameCount) { mFirstLetterIndex.clear(); mOnlyFolders = true; @@ -560,6 +570,14 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending continue; } + // Game count, which will be displayed in the system view. + if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) { + if (!mChildren[i]->getFavorite()) + gameCount.first++; + else + gameCount.second++; + } + if (foldersOnTop && mChildren[i]->getType() == FOLDER) { if (!mChildren[i]->getFavorite()) mChildrenFolders.push_back(mChildren[i]); @@ -664,13 +682,13 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending for (auto it = mChildrenFavoritesFolders.cbegin(); it != mChildrenFavoritesFolders.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending); + (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); } // Iterate through any child folders. for (auto it = mChildrenFolders.cbegin(); it != mChildrenFolders.cend(); it++) { if ((*it)->getChildren().size() > 0) - (*it)->sortFavoritesOnTop(comparator, ascending); + (*it)->sortFavoritesOnTop(comparator, ascending, gameCount); } if (!ascending) { @@ -695,10 +713,12 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending void FileData::sort(const SortType& type, bool mFavoritesOnTop) { + mGameCount = std::make_pair(0, 0); + if (mFavoritesOnTop) - sortFavoritesOnTop(*type.comparisonFunction, type.ascending); + sortFavoritesOnTop(*type.comparisonFunction, type.ascending, mGameCount); else - sort(*type.comparisonFunction, type.ascending); + sort(*type.comparisonFunction, type.ascending, mGameCount); } FileData::SortType FileData::getSortTypeFromString(std::string desc) { diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index 3ce89b3f2..943b2e0f0 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -51,6 +51,7 @@ public: const bool getFavorite(); const bool getHidden(); const bool getCountAsGame(); + const std::pair getGameCount() { return mGameCount; }; const bool getExcludeFromScraper(); const std::vector getChildrenRecursive() const; inline FileType getType() const { return mType; } @@ -123,8 +124,10 @@ public: description(sortDescription) {} }; - void sort(ComparisonFunction& comparator, bool ascending = true); - void sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending = true); + void sort(ComparisonFunction& comparator, bool ascending, + std::pair& gameCount); + void sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending, + std::pair& gameCount); void sort(const SortType& type, bool mFavoritesOnTop = false); MetaDataList metadata; @@ -151,6 +154,8 @@ private: std::vector mChildren; std::vector mFilteredChildren; std::vector mFirstLetterIndex; + // The pair includes non-favorite games, and favorite games. + std::pair mGameCount; bool mOnlyFolders; // Used for flagging a game for deletion from its gamelist.xml file. bool mDeletionFlag; diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index f4ab91ad0..77de87fcc 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1,4 +1,6 @@ +// SPDX-License-Identifier: MIT // +// EmulationStation Desktop Edition // SystemData.cpp // // Provides data structures for the game systems and populates and indexes them based @@ -12,6 +14,7 @@ #include "resources/ResourceManager.h" #include "utils/FileSystemUtil.h" #include "utils/StringUtil.h" +#include "views/UIModeController.h" #include "CollectionSystemManager.h" #include "FileFilterIndex.h" #include "FileSorts.h" @@ -20,10 +23,9 @@ #include "Platform.h" #include "Settings.h" #include "ThemeData.h" -#include "views/UIModeController.h" -#include #include +#include std::vector SystemData::sSystemVector; @@ -32,12 +34,14 @@ SystemData::SystemData( const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, - bool CollectionSystem) + bool CollectionSystem, + bool CustomCollectionSystem) : mName(name), mFullName(fullName), mEnvData(envData), mThemeFolder(themeFolder), mIsCollectionSystem(CollectionSystem), + mIsCustomCollectionSystem(CustomCollectionSystem), mIsGameSystem(true), mScrapeFlag(false) { @@ -402,9 +406,11 @@ std::string SystemData::getConfigPath(bool forWrite) bool SystemData::isVisible() { - return (getDisplayedGameCount() > 0 || - (UIModeController::getInstance()->isUIModeFull() && mIsCollectionSystem) || - (mIsCollectionSystem && mName == "favorites")); + // This function doesn't make much sense at the moment; if a game system does not have any + // games available, it will not be processed during startup and will as such not exist. + // In the future this function may be used for an option to hide specific systems, but + // for the time being all systems will always be visible. + return true; } SystemData* SystemData::getNext() const @@ -488,11 +494,6 @@ bool SystemData::hasGamelist() const return (Utils::FileSystem::exists(getGamelistPath(false))); } -unsigned int SystemData::getGameCount() const -{ - return (unsigned int)mRootFolder->getFilesRecursive(GAME).size(); -} - SystemData* SystemData::getRandomSystem(const SystemData* currentSystem) { unsigned int total = 0; @@ -612,10 +613,12 @@ FileData* SystemData::getRandomGame(const FileData* currentGame) return gameList.at(target); } -unsigned int SystemData::getDisplayedGameCount() const +std::pair SystemData::getDisplayedGameCount() const { - // Pass the flag to only count games that are marked with the flag 'countasgame'. - return (unsigned int)mRootFolder->getFilesRecursive(GAME, true, false).size(); + // Return all games for the system which are marked as 'countasgame'. As this flag is set + // by default, normally most games will be included in the number returned from here. + // The actual game counting takes place in FileData during sorting. + return mRootFolder->getGameCount(); } void SystemData::loadTheme() diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index 160d82f21..d65437cc3 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -1,4 +1,6 @@ +// SPDX-License-Identifier: MIT // +// EmulationStation Desktop Edition // SystemData.h // // Provides data structures for the game systems and populates and indexes them based @@ -7,7 +9,6 @@ // loading. // -#pragma once #ifndef ES_APP_SYSTEM_DATA_H #define ES_APP_SYSTEM_DATA_H @@ -36,7 +37,8 @@ public: const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, - bool CollectionSystem = false); + bool CollectionSystem = false, + bool CustomCollectionSystem = false); ~SystemData(); @@ -60,8 +62,7 @@ public: bool hasGamelist() const; std::string getThemePath() const; - unsigned int getGameCount() const; - unsigned int getDisplayedGameCount() const; + std::pair getDisplayedGameCount() const; bool getScrapeFlag() { return mScrapeFlag; }; void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; } @@ -80,6 +81,7 @@ public: inline std::vector::const_reverse_iterator getRevIterator() const { return std::find(sSystemVector.crbegin(), sSystemVector.crend(), this); }; inline bool isCollection() { return mIsCollectionSystem; }; + inline bool isCustomCollection() { return mIsCustomCollectionSystem; }; inline bool isGameSystem() { return mIsGameSystem; }; bool isVisible(); @@ -101,6 +103,7 @@ public: private: bool mIsCollectionSystem; + bool mIsCustomCollectionSystem; bool mIsGameSystem; bool mScrapeFlag; // Only used by scraper GUI to remember which systems to scrape. std::string mName; diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 8f26ae9fd..0963d7a61 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -283,16 +283,25 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) Math::lerp(infoStartOpacity, 0.f, t) * 255)); }, static_cast(infoStartOpacity * (goFast ? 10 : 150))); - unsigned int gameCount = getSelected()->getDisplayedGameCount(); + std::pair gameCount = getSelected()->getDisplayedGameCount(); // Also change the text after we've fully faded out. setAnimation(infoFadeOut, 0, [this, gameCount] { std::stringstream ss; + unsigned int totalGameCount = gameCount.first + gameCount.second; if (!getSelected()->isGameSystem()) ss << "CONFIGURATION"; + else if (getSelected()->isCollection() && (getSelected()->getName() == "favorites")) + ss << totalGameCount << " GAME" << (totalGameCount == 1 ? " " : "S"); + // The 'recent' gamelist has probably been trimmed after sorting, so we'll cap it at + // its maximum limit of 50 games. + else if (getSelected()->isCollection() && (getSelected()->getName() == "recent")) + ss << (totalGameCount > 50 ? 50 : totalGameCount) << " GAME" << + (totalGameCount == 1 ? " " : "S"); else - ss << gameCount << " GAMES AVAILABLE"; + ss << totalGameCount << " GAME" << (totalGameCount == 1 ? " " : "S ") << "(" << + gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)"); mSystemInfo.setText(ss.str()); }, false, 1); diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index 0a8f274e5..eba7e7c92 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -200,7 +200,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) else favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); - if (favoritesSorting && static_cast(getName()) != "recent") { + if (favoritesSorting && static_cast( + mRoot->getSystem()->getName()) != "recent") { FileData* entryToSelect; // Add favorite flag. if (!getCursor()->getFavorite()) { @@ -300,9 +301,11 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) } else if (CollectionSystemManager::get()->toggleGameInCollection(entryToUpdate)) { // Jump to the first entry in the gamelist if the last favorite was unmarked. - if (foldersOnTop && removedLastFavorite) + if (foldersOnTop && removedLastFavorite && + !entryToUpdate->getSystem()->isCustomCollection()) setCursor(getFirstGameEntry()); - else if (removedLastFavorite) + else if (removedLastFavorite && + !entryToUpdate->getSystem()->isCustomCollection()) setCursor(getFirstEntry()); return true; } diff --git a/es-app/src/views/gamelist/ISimpleGameListView.h b/es-app/src/views/gamelist/ISimpleGameListView.h index 14632a76a..3b7bc7035 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.h +++ b/es-app/src/views/gamelist/ISimpleGameListView.h @@ -9,9 +9,9 @@ #ifndef ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H #define ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H +#include "views/gamelist/IGameListView.h" #include "components/ImageComponent.h" #include "components/TextComponent.h" -#include "views/gamelist/IGameListView.h" #include @@ -46,7 +46,6 @@ protected: ImageComponent mBackground; std::vector mThemeExtras; - std::stack mCursorStack; };