From 7cefe6a2bd841af9f59faefadbcca554344012e0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 11 Jun 2020 21:08:48 +0200 Subject: [PATCH] Moved quick selector letter index generation to gamelist sorting loop. This increases (non-optimized) sort time with around 1,5 - 4,5% but the game option GUI opens way faster now due to the caching of the letter index. Also made the quick selector more intuitive. --- es-app/src/FileData.cpp | 43 ++++++- es-app/src/FileData.h | 8 +- .../src/guis/GuiCollectionSystemsOptions.cpp | 8 +- es-app/src/guis/GuiGamelistOptions.cpp | 109 +++++------------- es-app/src/guis/GuiGamelistOptions.h | 6 +- 5 files changed, 85 insertions(+), 89 deletions(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index afefa0e77..ac258afc5 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -310,38 +310,77 @@ void FileData::removeChild(FileData* file) void FileData::sort(ComparisonFunction& comparator, bool ascending) { + mFirstLetterIndex.clear(); std::stable_sort(mChildren.begin(), mChildren.end(), comparator); 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)); + // Iterate through any child folders. if ((*it)->getChildren().size() > 0) (*it)->sort(comparator, ascending); } + // 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()); + if (!ascending) std::reverse(mChildren.begin(), mChildren.end()); } void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending) { + mFirstLetterIndex.clear(); std::vector mChildrenFavorites; std::vector mChildrenOthers; for (unsigned int i = 0; i < mChildren.size(); i++) { - if (mChildren[i]->getFavorite()) + if (mChildren[i]->getFavorite()) { mChildrenFavorites.push_back(mChildren[i]); - else + } + else { mChildrenOthers.push_back(mChildren[i]); + // Build mFirstLetterIndex. + const char firstChar = toupper(mChildren[i]->getSortName().front()); + mFirstLetterIndex.push_back(std::string(1, firstChar)); + } } + // If there are only favorites in the gamelist, it makes sense to still generate + // a letter index. For instance to be able to quick jump in the 'favorites' + // collection. Doing this additional work here only for the applicable gamelists is + // probably faster than building a redundant index for all gamelists during sorting. + if (mChildrenOthers.size() == 0 && mChildrenFavorites.size() > 0) { + for (unsigned int i = 0; i < mChildren.size(); i++) { + const char firstChar = toupper(mChildren[i]->getSortName().front()); + mFirstLetterIndex.push_back(std::string(1, firstChar)); + } + } + + // 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()); + + // If there were at least one favorite in the gamelist, insert the favorite + // unicode character in the first position. + if (mChildrenOthers.size() > 0 && mChildrenFavorites.size() > 0) + mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FAVORITE_CHAR); + // Sort favorite games and the other games separately. std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator); std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator); + // Iterate through any child folders. for (auto it = mChildrenFavorites.cbegin(); it != mChildrenFavorites.cend(); it++) { if ((*it)->getChildren().size() > 0) (*it)->sortFavoritesOnTop(comparator, ascending); } + // Iterate through any child folders. for (auto it = mChildrenOthers.cbegin(); it != mChildrenOthers.cend(); it++) { if ((*it)->getChildren().size() > 0) (*it)->sortFavoritesOnTop(comparator, ascending); diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index a2244977c..2016e9ec9 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -57,6 +57,8 @@ public: inline const std::vector& getChildren() const { return mChildren; } inline SystemData* getSystem() const { return mSystem; } inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } + const std::vector& getFirstLetterIndex() const + { return mFirstLetterIndex; }; static const std::string getMediaDirectory(); virtual const std::string getMediafilePath( std::string subdirectory, std::string mediatype) const; @@ -87,8 +89,7 @@ public: virtual FileData* getSourceFileData(); inline std::string getSystemName() const { return mSystemName; }; - // Returns our best guess at the "real" name for this file - // (will attempt to perform MAME name translation). + // Returns our best guess at the "real" name for this file. std::string getDisplayName() const; // As above, but also remove parenthesis. @@ -132,6 +133,9 @@ private: std::unordered_map mChildrenByFilename; std::vector mChildren; std::vector mFilteredChildren; + std::vector mFirstLetterIndex; + + const std::string FAVORITE_CHAR = "\uF005"; }; class CollectionFileData : public FileData diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 179c12322..fddcc9753 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -74,14 +74,14 @@ void GuiCollectionSystemsOptions::initializeMenu() mMenu.addRow(row); - bundleCustomCollections = std::make_shared(mWindow); - bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem")); - mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections); - sortFavFirstCustomSwitch = std::make_shared(mWindow); sortFavFirstCustomSwitch->setState(Settings::getInstance()->getBool("FavFirstCustom")); mMenu.addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", sortFavFirstCustomSwitch); + bundleCustomCollections = std::make_shared(mWindow); + bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem")); + mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections); + toggleSystemNameInCollections = std::make_shared(mWindow); toggleSystemNameInCollections->setState(Settings::getInstance()->getBool("CollectionShowSystemInfo")); mMenu.addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", toggleSystemNameInCollections); diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index bdde8ca01..3a0e54b24 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -47,79 +47,30 @@ GuiGamelistOptions::GuiGamelistOptions( mFavoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); if (!fromPlaceholder) { - // Jump to letter. + // Jump to letter quick selector. row.elements.clear(); - // Define supported character range. - // This range includes all numbers, capital letters, and most reasonable symbols. - char startChar = '!'; - char endChar = '_'; + // The letter index is generated in FileData during game system sorting. + mFirstLetterIndex = file->getParent()->getFirstLetterIndex(); - char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]); - if (curChar < startChar || curChar > endChar) - curChar = startChar; + // Set the quick selector to the first character of the selected game. + if (mFavoritesSorting && file->getFavorite() && mFirstLetterIndex.front() == FAVORITE_CHAR) + mCurrentFirstCharacter = FAVORITE_CHAR; + else + mCurrentFirstCharacter = toupper(file->getSortName().front()); mJumpToLetterList = std::make_shared(mWindow, getHelpStyle(), "JUMP TO...", false); - if (mFavoritesSorting && system->getName() != "favorites" && - system->getName() != "recent") { - // Check whether the first game in the list is a favorite, if it's - // not, then there are no favorites currently visible in this gamelist. - if (getGamelist()->getCursor()->getParent()->getChildrenListToDisplay()[0]-> - getFavorite()) { - if (getGamelist()->getCursor()->getFavorite()) - mJumpToLetterList->add(FAVORITE_CHAR, FAVORITE_CHAR, 1); - else - mJumpToLetterList->add(FAVORITE_CHAR, FAVORITE_CHAR, 0); - } + // Populate the quick selector. + for (unsigned int i = 0; i < mFirstLetterIndex.size(); i++) { + mJumpToLetterList->add(mFirstLetterIndex[i], mFirstLetterIndex[i], 0); + if (mFirstLetterIndex[i] == mCurrentFirstCharacter) + mJumpToLetterList->selectEntry(i); } - for (char c = startChar; c <= endChar; c++) { - // Check if c is a valid first letter in the current list. - const std::vector& files = - getGamelist()->getCursor()->getParent()->getChildrenListToDisplay(); - for (auto file : files) { - char candidate = (char)toupper(file->getSortName()[0]); - if (c == candidate) { - // If the game is a favorite, continue to the next entry in the list. - if (mFavoritesSorting && system->getName() != "favorites" && - system->getName() != "recent" && file->getFavorite()) - continue; - - // If the currently selected game is a favorite, set the character - // as not selected so we don't get two current positions. - if (mFavoritesSorting && system->getName() != "favorites" && - system->getName() != "recent" && - getGamelist()->getCursor()->getFavorite()) - mJumpToLetterList->add(std::string(1, c), std::string(1, c), 0); - else - mJumpToLetterList->add(std::string(1, c), std::string(1, c), c == curChar); - break; - } - } - } - - row.addElement(std::make_shared( - mWindow, "JUMP TO...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true); - row.addElement(mJumpToLetterList, false); - row.input_handler = [&](InputConfig* config, Input input) { - if (config->isMappedTo("a", input) && input.value) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); - if (mJumpToLetterList->getSelected() == FAVORITE_CHAR) - jumpToFirstRow(); - else - jumpToLetter(); - - return true; - } - else if (mJumpToLetterList->input(config, input)) { - return true; - } - return false; - }; if (system->getName() != "recent") - mMenu.addRow(row); + 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); @@ -201,26 +152,30 @@ GuiGamelistOptions::~GuiGamelistOptions() // If a new sorting type was selected, then sort and update mSortTypeString for the system. if ((*mListSort->getSelected()).description != root->getSortTypeString()) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); - // This will also recursively sort children. root->sort(*mListSort->getSelected(), mFavoritesSorting); root->setSortTypeString((*mListSort->getSelected()).description); - // Select the first row of the gamelist. - FileData* firstRow = getGamelist()->getCursor()->getParent()-> - getChildrenListToDisplay()[0]; - getGamelist()->setCursor(firstRow); - - // Notify that the root folder was sorted. + // Notify that the root folder was sorted (refresh). getGamelist()->onFileChanged(root, FILE_SORTED); } + + // Has the user changed the letter using the quick selector? + if (mCurrentFirstCharacter != mJumpToLetterList->getSelected()) { + if (mJumpToLetterList->getSelected() == FAVORITE_CHAR) + jumpToFirstRow(); + else + jumpToLetter(); + } } + if (mFiltersChanged) { // Only reload full view if we came from a placeholder as we need to // re-display the remaining elements for whatever new game is selected. ViewController::get()->reloadGameListView(mSystem); } + + NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); } void GuiGamelistOptions::openGamelistFilter() @@ -283,29 +238,27 @@ void GuiGamelistOptions::openMetaDataEd() void GuiGamelistOptions::jumpToLetter() { - char letter = mJumpToLetterList->getSelected()[0]; + char letter = mJumpToLetterList->getSelected().front(); // Get first row of the gamelist. const std::vector& files = getGamelist()->getCursor()-> getParent()->getChildrenListToDisplay(); for (unsigned int i = 0; i < files.size(); i++) { - if (mFavoritesSorting && mSystem->getName() != "favorites") { - if ((char)toupper(files.at(i)->getSortName()[0]) == + if (mFavoritesSorting && mFirstLetterIndex.front() == FAVORITE_CHAR) { + if ((char)toupper(files.at(i)->getSortName().front()) == letter && !files.at(i)->getFavorite()) { getGamelist()->setCursor(files.at(i)); break; } } else { - if ((char)toupper(files.at(i)->getSortName()[0]) == letter) { + if ((char)toupper(files.at(i)->getSortName().front()) == letter) { getGamelist()->setCursor(files.at(i)); break; } } } - - delete this; } void GuiGamelistOptions::jumpToFirstRow() @@ -314,8 +267,6 @@ void GuiGamelistOptions::jumpToFirstRow() const std::vector& files = getGamelist()->getCursor()-> getParent()->getChildrenListToDisplay(); getGamelist()->setCursor(files.at(0)); - - delete this; } bool GuiGamelistOptions::input(InputConfig* config, Input input) diff --git a/es-app/src/guis/GuiGamelistOptions.h b/es-app/src/guis/GuiGamelistOptions.h index 67d7e9420..57a1aa0fb 100644 --- a/es-app/src/guis/GuiGamelistOptions.h +++ b/es-app/src/guis/GuiGamelistOptions.h @@ -40,8 +40,6 @@ private: void jumpToLetter(); void jumpToFirstRow(); - const std::string FAVORITE_CHAR = "\uF005"; - MenuComponent mMenu; typedef OptionListComponent LetterList; @@ -56,6 +54,10 @@ private: bool fromPlaceholder; bool mFiltersChanged; bool mCancelled; + std::vector mFirstLetterIndex; + std::string mCurrentFirstCharacter; + const std::string FAVORITE_CHAR = "\uF005"; + }; #endif // ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H