Moved game counting to the sort function and improved the custom collection handling.

This commit is contained in:
Leon Styhre 2020-09-21 18:13:27 +02:00
parent c5ecfb4e36
commit 1c831249da
10 changed files with 100 additions and 51 deletions

View file

@ -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

View file

@ -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 <pugixml.hpp>
#include <fstream>
#include <pugixml.hpp>
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;

View file

@ -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<std::string> getUserCollectionThemeFolders();
void trimCollectionCount(FileData* rootFolder, int limit);
bool themeFolderExists(std::string folder);
bool includeFileInAutoCollections(FileData* file);
SystemData* mCustomCollectionsBundle;

View file

@ -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<unsigned int, unsigned int>& 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<unsigned int, unsigned int>& 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) {

View file

@ -51,6 +51,7 @@ public:
const bool getFavorite();
const bool getHidden();
const bool getCountAsGame();
const std::pair<unsigned int, unsigned int> getGameCount() { return mGameCount; };
const bool getExcludeFromScraper();
const std::vector<FileData*> 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<unsigned int, unsigned int>& gameCount);
void sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending,
std::pair<unsigned int, unsigned int>& gameCount);
void sort(const SortType& type, bool mFavoritesOnTop = false);
MetaDataList metadata;
@ -151,6 +154,8 @@ private:
std::vector<FileData*> mChildren;
std::vector<FileData*> mFilteredChildren;
std::vector<std::string> mFirstLetterIndex;
// The pair includes non-favorite games, and favorite games.
std::pair<unsigned int, unsigned int> mGameCount;
bool mOnlyFolders;
// Used for flagging a game for deletion from its gamelist.xml file.
bool mDeletionFlag;

View file

@ -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 <pugixml.hpp>
#include <fstream>
#include <pugixml.hpp>
std::vector<SystemData*> 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<unsigned int, unsigned int> 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()

View file

@ -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<unsigned int, unsigned int> getDisplayedGameCount() const;
bool getScrapeFlag() { return mScrapeFlag; };
void setScrapeFlag(bool scrapeflag) { mScrapeFlag = scrapeflag; }
@ -80,6 +81,7 @@ public:
inline std::vector<SystemData*>::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;

View file

@ -283,16 +283,25 @@ void SystemView::onCursorChanged(const CursorState& /*state*/)
Math::lerp(infoStartOpacity, 0.f, t) * 255));
}, static_cast<int>(infoStartOpacity * (goFast ? 10 : 150)));
unsigned int gameCount = getSelected()->getDisplayedGameCount();
std::pair<unsigned int, unsigned int> 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);

View file

@ -200,7 +200,8 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (favoritesSorting && static_cast<std::string>(getName()) != "recent") {
if (favoritesSorting && static_cast<std::string>(
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;
}

View file

@ -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 <stack>
@ -46,7 +46,6 @@ protected:
ImageComponent mBackground;
std::vector<GuiComponent*> mThemeExtras;
std::stack<FileData*> mCursorStack;
};