mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 07:35:38 +00:00
Multiple improvements to the handling of custom collections.
Also improved the random game and random system functions.
This commit is contained in:
parent
844d733c89
commit
1a0b2f8bb7
25
USERGUIDE.md
25
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
|
||||||
~/ROMs/c64/Multidisk/Last Ninja 2/LNINJA2A.D64
|
~/ROMs/c64/Multidisk/Last Ninja 2/LNINJA2A.D64
|
||||||
~/ROMs/c64/Multidisk/Last Ninja 2/LNINJA2B.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-E0.d64
|
||||||
~/ROMs/c64/Multidisk/Pirates/PIRAT-E1.d64
|
~/ROMs/c64/Multidisk/Pirates/PIRAT-E1.d64
|
||||||
~/ROMs/c64/Multidisk/Pirates/PIRAT-E2.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.
|
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
|
LNINJA2A.D64
|
||||||
LNINJA2B.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).
|
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.
|
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.
|
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'.
|
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.
|
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.
|
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.
|
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
|
## Themes
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
std::string myCollectionsName = "collections";
|
std::string myCollectionsName = "collections";
|
||||||
|
|
||||||
|
@ -272,11 +273,6 @@ void CollectionSystemManager::updateSystemsList()
|
||||||
if (rootFolder->getChildren().size() > 0) {
|
if (rootFolder->getChildren().size() > 0) {
|
||||||
rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->
|
rootFolder->sort(rootFolder->getSortTypeFromString(rootFolder->
|
||||||
getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom"));
|
getSortTypeString()), Settings::getInstance()->getBool("FavFirstCustom"));
|
||||||
// Update the custom collections metadata now that the sorting has been done.
|
|
||||||
std::vector<FileData*> customCollections = rootFolder->getChildren();
|
|
||||||
for (auto it = customCollections.cbegin(); it != customCollections.cend(); it++)
|
|
||||||
updateCollectionFolderMetadata((*it)->getSystem());
|
|
||||||
|
|
||||||
SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
|
SystemData::sSystemVector.push_back(mCustomCollectionsBundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,7 +666,6 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file)
|
||||||
systemViewToUpdate->getIndex()->addToIndex(newGame);
|
systemViewToUpdate->getIndex()->addToIndex(newGame);
|
||||||
refreshCollectionSystems(newGame);
|
refreshCollectionSystems(newGame);
|
||||||
}
|
}
|
||||||
updateCollectionFolderMetadata(sysData);
|
|
||||||
saveCustomCollection(sysData);
|
saveCustomCollection(sysData);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -737,15 +732,35 @@ void CollectionSystemManager::initAutoCollectionSystems()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to generate a description of the collection, all other metadata fields are hidden.
|
// 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();
|
FileData* rootFolder = sys->getRootFolder();
|
||||||
rootFolder->metadata.set("hidemetadata", "true");
|
|
||||||
|
|
||||||
std::string desc = "This collection is empty.";
|
std::string desc = "This collection is empty.";
|
||||||
std::vector<FileData*> gamesList = rootFolder->getChildren();
|
std::vector<FileData*> gamesList = rootFolder->getChildren();
|
||||||
|
std::vector<FileData*> gamesListRandom;
|
||||||
unsigned int gameCount = gamesList.size();
|
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<int> uniform_dist(0, gameCount - 1 - i);
|
||||||
|
target = uniform_dist(engine);
|
||||||
|
gamesListRandom.push_back(gamesList[target]);
|
||||||
|
std::vector<FileData*>::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) {
|
if (gameCount > 0) {
|
||||||
switch (gameCount) {
|
switch (gameCount) {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -772,6 +787,12 @@ void CollectionSystemManager::updateCollectionFolderMetadata(SystemData* sys)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootFolder->metadata.set("desc", desc);
|
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()
|
void CollectionSystemManager::initCustomCollectionSystems()
|
||||||
|
|
|
@ -91,7 +91,7 @@ public:
|
||||||
bool toggleGameInCollection(FileData* file);
|
bool toggleGameInCollection(FileData* file);
|
||||||
|
|
||||||
SystemData* getSystemToView(SystemData* sys);
|
SystemData* getSystemToView(SystemData* sys);
|
||||||
void updateCollectionFolderMetadata(SystemData* sys);
|
FileData* updateCollectionFolderMetadata(SystemData* sys);
|
||||||
|
|
||||||
bool getIsCustomCollection(SystemData* system);
|
bool getIsCustomCollection(SystemData* system);
|
||||||
|
|
||||||
|
|
|
@ -447,6 +447,25 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending,
|
||||||
std::vector<FileData*> mChildrenFolders;
|
std::vector<FileData*> mChildrenFolders;
|
||||||
std::vector<FileData*> mChildrenOthers;
|
std::vector<FileData*> 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) {
|
if (foldersOnTop) {
|
||||||
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
||||||
if (mChildren[i]->getType() == FOLDER) {
|
if (mChildren[i]->getType() == FOLDER) {
|
||||||
|
@ -545,6 +564,25 @@ void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending
|
||||||
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
bool foldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
||||||
bool hasFolders = false;
|
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++) {
|
for (unsigned int i = 0; i < mChildren.size(); i++) {
|
||||||
// Game count, which will be displayed in the system view.
|
// Game count, which will be displayed in the system view.
|
||||||
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
|
if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
std::vector<SystemData*> SystemData::sSystemVector;
|
std::vector<SystemData*> SystemData::sSystemVector;
|
||||||
|
|
||||||
|
@ -528,7 +529,11 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Get a random number in range.
|
// 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<int> uniform_dist(0, total - 1);
|
||||||
|
int target = uniform_dist(engine);
|
||||||
|
|
||||||
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++) {
|
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++) {
|
||||||
if ((*it)->isGameSystem()) {
|
if ((*it)->isGameSystem()) {
|
||||||
|
@ -547,33 +552,11 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem)
|
||||||
return randomSystem;
|
return randomSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileData* SystemData::getRandomCollectionFolder(const FileData* currentFolder)
|
|
||||||
{
|
|
||||||
if (!currentFolder)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
std::vector<FileData*> 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)
|
FileData* SystemData::getRandomGame(const FileData* currentGame)
|
||||||
{
|
{
|
||||||
std::vector<FileData*> gameList = mRootFolder->getFilesRecursive(GAME, true);
|
std::vector<FileData*> gameList = mRootFolder->getFilesRecursive(GAME, true);
|
||||||
|
|
||||||
if (gameList.size() == 1)
|
if (!currentGame && gameList.size() == 1)
|
||||||
return gameList.front();
|
return gameList.front();
|
||||||
|
|
||||||
if (currentGame && currentGame->getType() == PLACEHOLDER)
|
if (currentGame && currentGame->getType() == PLACEHOLDER)
|
||||||
|
@ -628,7 +611,11 @@ FileData* SystemData::getRandomGame(const FileData* currentGame)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Get a random number in range.
|
// 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<int> uniform_dist(0, total - 1);
|
||||||
|
target = uniform_dist(engine);
|
||||||
}
|
}
|
||||||
while (currentGame && gameList.at(target) == currentGame);
|
while (currentGame && gameList.at(target) == currentGame);
|
||||||
|
|
||||||
|
@ -648,6 +635,13 @@ void SystemData::sortSystem(bool reloadGamelist)
|
||||||
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
|
favoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
|
||||||
|
|
||||||
FileData* rootFolder = getRootFolder();
|
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);
|
setupSystemSortType(rootFolder);
|
||||||
|
|
||||||
rootFolder->sort(rootFolder->getSortTypeFromString(
|
rootFolder->sort(rootFolder->getSortTypeFromString(
|
||||||
|
|
|
@ -90,7 +90,6 @@ public:
|
||||||
SystemData* getNext() const;
|
SystemData* getNext() const;
|
||||||
SystemData* getPrev() const;
|
SystemData* getPrev() const;
|
||||||
static SystemData* getRandomSystem(const SystemData* currentSystem);
|
static SystemData* getRandomSystem(const SystemData* currentSystem);
|
||||||
static FileData* getRandomCollectionFolder(const FileData* currentFolder);
|
|
||||||
FileData* getRandomGame(const FileData* currentGame = nullptr);
|
FileData* getRandomGame(const FileData* currentGame = nullptr);
|
||||||
|
|
||||||
void sortSystem(bool reloadGamelist = true);
|
void sortSystem(bool reloadGamelist = true);
|
||||||
|
|
|
@ -32,7 +32,9 @@ GuiGamelistOptions::GuiGamelistOptions(
|
||||||
mMenu(window, "OPTIONS"),
|
mMenu(window, "OPTIONS"),
|
||||||
fromPlaceholder(false),
|
fromPlaceholder(false),
|
||||||
mFiltersChanged(false),
|
mFiltersChanged(false),
|
||||||
mCancelled(false)
|
mCancelled(false),
|
||||||
|
isCustomCollection(false),
|
||||||
|
isCustomCollectionGroup(false)
|
||||||
{
|
{
|
||||||
addChild(&mMenu);
|
addChild(&mMenu);
|
||||||
|
|
||||||
|
@ -43,6 +45,14 @@ GuiGamelistOptions::GuiGamelistOptions(
|
||||||
fromPlaceholder = file->isPlaceHolder();
|
fromPlaceholder = file->isPlaceHolder();
|
||||||
ComponentListRow row;
|
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.
|
// 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.
|
// Also check if the gamelist only contains folders, as generated by the FileData sorting.
|
||||||
mFoldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
mFoldersOnTop = Settings::getInstance()->getBool("FoldersOnTop");
|
||||||
|
@ -96,31 +106,42 @@ GuiGamelistOptions::GuiGamelistOptions(
|
||||||
if (system->getName() != "recent")
|
if (system->getName() != "recent")
|
||||||
mMenu.addWithLabel("JUMP TO..", mJumpToLetterList);
|
mMenu.addWithLabel("JUMP TO..", mJumpToLetterList);
|
||||||
|
|
||||||
// Sort list by selected sort type (persistent throughout the program session).
|
// Add the sorting entry, unless this is the grouped custom collections list.
|
||||||
mListSort = std::make_shared<SortList>(mWindow, getHelpStyle(), "SORT GAMES BY", false);
|
if (!isCustomCollectionGroup) {
|
||||||
FileData* root = mSystem->getRootFolder();
|
// Sort list by selected sort type (persistent throughout the program session).
|
||||||
std::string sortType = root->getSortTypeString();
|
mListSort = std::make_shared<SortList>(mWindow, getHelpStyle(), "SORT GAMES BY", false);
|
||||||
|
FileData* root;
|
||||||
for (unsigned int i = 0; i <FileSorts::SortTypes.size(); i++) {
|
if (isCustomCollection)
|
||||||
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
root = getGamelist()->getCursor()->getSystem()->getRootFolder();
|
||||||
if (sort.description == sortType)
|
|
||||||
mListSort->add(sort.description, &sort, 1);
|
|
||||||
else
|
else
|
||||||
mListSort->add(sort.description, &sort, 0);
|
root = mSystem->getRootFolder();
|
||||||
|
|
||||||
|
std::string sortType = root->getSortTypeString();
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i <FileSorts::SortTypes.size(); i++) {
|
||||||
|
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
|
||||||
|
if (sort.description == sortType)
|
||||||
|
mListSort->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.
|
// Add the filters entry, unless this is the grouped custom collections list.
|
||||||
if (system->getName() != "recent" && !Settings::getInstance()->getBool("ForceDisableFilters")) {
|
if (!isCustomCollectionGroup) {
|
||||||
row.elements.clear();
|
if (system->getName() != "recent" &&
|
||||||
row.addElement(std::make_shared<TextComponent>
|
!Settings::getInstance()->getBool("ForceDisableFilters")) {
|
||||||
(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
row.elements.clear();
|
||||||
row.addElement(makeArrow(mWindow), false);
|
row.addElement(std::make_shared<TextComponent>
|
||||||
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
|
(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
|
||||||
mMenu.addRow(row);
|
row.addElement(makeArrow(mWindow), false);
|
||||||
|
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
|
||||||
|
mMenu.addRow(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, CollectionSystemData> customCollections =
|
std::map<std::string, CollectionSystemData> customCollections =
|
||||||
|
@ -194,24 +215,21 @@ GuiGamelistOptions::~GuiGamelistOptions()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!fromPlaceholder) {
|
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 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.
|
// This will also recursively sort children.
|
||||||
root->sort(*mListSort->getSelected(), mFavoritesSorting);
|
root->sort(*mListSort->getSelected(), mFavoritesSorting);
|
||||||
root->setSortTypeString((*mListSort->getSelected()).description);
|
root->setSortTypeString((*mListSort->getSelected()).description);
|
||||||
|
|
||||||
// Notify that the root folder was sorted (refresh).
|
// Notify that the root folder was sorted (refresh).
|
||||||
getGamelist()->onFileChanged(root, FILE_SORTED);
|
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<FileData*> 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?
|
// Has the user changed the letter using the quick selector?
|
||||||
|
|
|
@ -57,6 +57,8 @@ private:
|
||||||
bool fromPlaceholder;
|
bool fromPlaceholder;
|
||||||
bool mFiltersChanged;
|
bool mFiltersChanged;
|
||||||
bool mCancelled;
|
bool mCancelled;
|
||||||
|
bool isCustomCollection;
|
||||||
|
bool isCustomCollectionGroup;
|
||||||
std::vector<std::string> mFirstLetterIndex;
|
std::vector<std::string> mFirstLetterIndex;
|
||||||
std::string mCurrentFirstCharacter;
|
std::string mCurrentFirstCharacter;
|
||||||
std::string FAVORITE_CHAR;
|
std::string FAVORITE_CHAR;
|
||||||
|
|
|
@ -375,16 +375,6 @@ void GuiMenu::openUISettings()
|
||||||
|
|
||||||
(*it)->sortSystem();
|
(*it)->sortSystem();
|
||||||
|
|
||||||
// Update the metadata for any custom collections.
|
|
||||||
if ((*it)->isCollection() && (*it)->getFullName() == "collections") {
|
|
||||||
std::vector<FileData*> 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.
|
// Jump to the first row of the gamelist.
|
||||||
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
|
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
|
||||||
gameList->setCursor(gameList->getFirstEntry());
|
gameList->setCursor(gameList->getFirstEntry());
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
#include "animations/LambdaAnimation.h"
|
#include "animations/LambdaAnimation.h"
|
||||||
#include "views/ViewController.h"
|
#include "views/ViewController.h"
|
||||||
|
#include "CollectionSystemManager.h"
|
||||||
|
#include "SystemData.h"
|
||||||
|
|
||||||
#define FADE_IN_START_OPACITY 0.5f
|
#define FADE_IN_START_OPACITY 0.5f
|
||||||
#define FADE_IN_TIME 300
|
#define FADE_IN_TIME 300
|
||||||
|
@ -240,15 +242,30 @@ void DetailedGameListView::updateInfoPanel()
|
||||||
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected();
|
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 the game data has already been rendered to the info panel, then skip it this time.
|
||||||
if (file == mLastUpdated) {
|
if (file == mLastUpdated)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
mLastUpdated = file;
|
if (!mList.isScrolling())
|
||||||
|
mLastUpdated = file;
|
||||||
|
|
||||||
bool hideMetaDataFields = false;
|
bool hideMetaDataFields = false;
|
||||||
|
|
||||||
if (file)
|
if (file) {
|
||||||
hideMetaDataFields = (file->metadata.get("hidemetadata") == "true");
|
// 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) {
|
if (hideMetaDataFields) {
|
||||||
mLblRating.setVisible(false);
|
mLblRating.setVisible(false);
|
||||||
|
@ -292,9 +309,29 @@ void DetailedGameListView::updateInfoPanel()
|
||||||
fadingOut = true;
|
fadingOut = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mThumbnail.setImage(file->getThumbnailPath());
|
// If we're browsing a grouped custom collection, then update the folder metadata
|
||||||
mMarquee.setImage(file->getMarqueePath());
|
// which will generate a description of three random games and return a pointer to
|
||||||
mImage.setImage(file->getImagePath());
|
// 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.
|
// Fade in the game image.
|
||||||
auto func = [this](float t) {
|
auto func = [this](float t) {
|
||||||
|
|
|
@ -166,14 +166,6 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
|
||||||
FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor());
|
FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor());
|
||||||
if (randomGame)
|
if (randomGame)
|
||||||
setCursor(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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
#if defined(_RPI_)
|
#if defined(_RPI_)
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "CollectionSystemManager.h"
|
||||||
|
#include "SystemData.h"
|
||||||
|
|
||||||
#define FADE_IN_START_OPACITY 0.5f
|
#define FADE_IN_START_OPACITY 0.5f
|
||||||
#define FADE_IN_TIME 650
|
#define FADE_IN_TIME 650
|
||||||
|
@ -52,7 +54,8 @@ VideoGameListView::VideoGameListView(
|
||||||
mPlayers(window),
|
mPlayers(window),
|
||||||
mLastPlayed(window),
|
mLastPlayed(window),
|
||||||
mPlayCount(window),
|
mPlayCount(window),
|
||||||
mName(window)
|
mName(window),
|
||||||
|
mLastUpdated(nullptr)
|
||||||
{
|
{
|
||||||
const float padding = 0.01f;
|
const float padding = 0.01f;
|
||||||
|
|
||||||
|
@ -264,15 +267,30 @@ void VideoGameListView::updateInfoPanel()
|
||||||
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected();
|
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 the game data has already been rendered to the info panel, then skip it this time.
|
||||||
if (file == mLastUpdated) {
|
if (file == mLastUpdated)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
mLastUpdated = file;
|
if (!mList.isScrolling())
|
||||||
|
mLastUpdated = file;
|
||||||
|
|
||||||
bool hideMetaDataFields = false;
|
bool hideMetaDataFields = false;
|
||||||
|
|
||||||
if (file)
|
if (file) {
|
||||||
hideMetaDataFields = (file->metadata.get("hidemetadata") == "true");
|
// 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) {
|
if (hideMetaDataFields) {
|
||||||
mLblRating.setVisible(false);
|
mLblRating.setVisible(false);
|
||||||
|
@ -317,12 +335,44 @@ void VideoGameListView::updateInfoPanel()
|
||||||
fadingOut = true;
|
fadingOut = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mThumbnail.setImage(file->getThumbnailPath());
|
// If we're browsing a grouped custom collection, then update the folder metadata
|
||||||
mMarquee.setImage(file->getMarqueePath());
|
// which will generate a description of three random games and return a pointer to
|
||||||
mVideo->setImage(file->getImagePath());
|
// 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()))
|
if (!mVideo->setVideo(randomGame->getVideoPath()))
|
||||||
mVideo->setDefaultVideo();
|
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;
|
mVideoPlaying = true;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
Missing systems
|
Missing systems
|
||||||
===============
|
===============
|
||||||
* Themes for custom collections such as "Fighting games", "Driving games", "Shoot 'em up" etc.
|
|
||||||
|
|
||||||
|
|
||||||
Missing (or not updated) theme data
|
Missing (or not updated) theme data
|
||||||
|
|
Loading…
Reference in a new issue