Monster commit. Rewrote gamelist sorting logic and made per-gamelist sort settings session-permanent. Cleaned up a lot of code and started to reformat for 100 characters line length.

This commit is contained in:
Leon Styhre 2020-05-24 10:29:29 +02:00
parent f806285e06
commit f2f7d34bb6
35 changed files with 2288 additions and 1825 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,22 @@
//
// CollectionSystemManager.h
//
// Manages collections of the following two types:
// 1) Automatically populated (All games, Favorites and Recent/Last Played)
// 2) Custom/user-created (could be any number of these)
//
// The automatic collections are basically virtual systems that have no
// gamelist.xml files and that only exist in memory during the program session.
// SystemData sets up the basic data structures and CollectionSystemManager
// populates and manages the collections.
//
// The custom collections have simple data files which are just lists of ROM files.
//
// In addition to this, CollectionSystemManager also handles some logic for
// normal systems such as adding and removing favorite games, including triggering
// 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
@ -11,26 +30,22 @@ class SystemData;
class Window;
struct SystemEnvironmentData;
enum CollectionSystemType
{
enum CollectionSystemType {
AUTO_ALL_GAMES,
AUTO_LAST_PLAYED,
AUTO_FAVORITES,
CUSTOM_COLLECTION
};
struct CollectionSystemDecl
{
CollectionSystemType type; // type of system
struct CollectionSystemDecl {
CollectionSystemType type;
std::string name;
std::string longName;
std::string defaultSort;
std::string themeFolder;
bool isCustom;
};
struct CollectionSystemData
{
struct CollectionSystemData {
SystemData* system;
CollectionSystemDecl decl;
bool isEnabled;
@ -57,8 +72,10 @@ public:
void updateCollectionSystem(FileData* file, CollectionSystemData sysData);
void deleteCollectionFiles(FileData* file);
inline std::map<std::string, CollectionSystemData> getAutoCollectionSystems() { return mAutoCollectionSystemsData; };
inline std::map<std::string, CollectionSystemData> getCustomCollectionSystems() { return mCustomCollectionSystemsData; };
inline std::map<std::string, CollectionSystemData> getAutoCollectionSystems()
{ return mAutoCollectionSystemsData; };
inline std::map<std::string, CollectionSystemData> getCustomCollectionSystems()
{ return mCustomCollectionSystemsData; };
inline SystemData* getCustomCollectionsBundle() { return mCustomCollectionsBundle; };
std::vector<std::string> getUnusedSystemsFromTheme();
SystemData* addNewCustomCollection(std::string name);
@ -76,6 +93,8 @@ public:
SystemData* getSystemToView(SystemData* sys);
void updateCollectionFolderMetadata(SystemData* sys);
bool getIsCustomCollection(SystemData* system);
private:
static CollectionSystemManager* sInstance;
SystemEnvironmentData* mCollectionEnvData;
@ -90,12 +109,14 @@ private:
void initAutoCollectionSystems();
void initCustomCollectionSystems();
SystemData* getAllGamesCollection();
SystemData* createNewCollectionEntry(std::string name, CollectionSystemDecl sysDecl, bool index = true);
SystemData* createNewCollectionEntry(std::string name,
CollectionSystemDecl sysDecl, bool index = true);
void populateAutoCollection(CollectionSystemData* sysData);
void populateCustomCollection(CollectionSystemData* sysData);
void removeCollectionsFromDisplayedSystems();
void addEnabledCollectionsToDisplayedSystems(std::map<std::string, CollectionSystemData>* colSystemData);
void addEnabledCollectionsToDisplayedSystems(std::map<std::string,
CollectionSystemData>* colSystemData);
std::vector<std::string> getSystemsFromConfig();
std::vector<std::string> getSystemsFromTheme();

View file

@ -1,3 +1,11 @@
//
// FileData.cpp
//
// Provides game file data structures and functions to access and sort this information.
// Also provides functions to look up paths to media files and for launching games
// (launching initiated by the ViewController).
//
#include "FileData.h"
#include "utils/FileSystemUtil.h"
@ -16,10 +24,21 @@
#include "Window.h"
#include <assert.h>
FileData::FileData(FileType type, const std::string& path, SystemEnvironmentData* envData, SystemData* system)
: mType(type), mPath(path), mSystem(system), mEnvData(envData), mSourceFileData(NULL), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor!
FileData::FileData(
FileType type,
const std::string& path,
SystemEnvironmentData* envData,
SystemData* system)
: mType(type),
mPath(path),
mSystem(system),
mEnvData(envData),
mSourceFileData(nullptr),
mParent(nullptr),
// Metadata is REALLY set in the constructor!
metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA)
{
// metadata needs at least a name field (since that's what getName() will return)
// Metadata needs at least a name field (since that's what getName() will return).
if (metadata.get("name").empty())
metadata.set("name", getDisplayName());
mSystemName = system->getName();
@ -40,9 +59,6 @@ FileData::~FileData()
std::string FileData::getDisplayName() const
{
std::string stem = Utils::FileSystem::getStem(mPath);
// if(mSystem && mSystem->hasPlatformId(PlatformIds::ARCADE) || mSystem->hasPlatformId(PlatformIds::NEOGEO))
// stem = MameNames::getInstance()->getRealName(stem);
return stem;
}
@ -77,26 +93,20 @@ const std::string FileData::getMediaDirectory() const
std::string mediaDirSetting = Settings::getInstance()->getString("MediaDirectory");
std::string mediaDirPath = "";
if(mediaDirSetting == "")
{
if (mediaDirSetting == "") {
mediaDirPath = Utils::FileSystem::getHomePath() + "/.emulationstation/downloaded_media/";
}
else
{
else {
mediaDirPath = mediaDirSetting;
// Expand home symbol if the path starts with ~
if(mediaDirPath[0] == '~')
{
if (mediaDirPath[0] == '~') {
mediaDirPath.erase(0, 1);
mediaDirPath.insert(0, Utils::FileSystem::getHomePath());
}
if (mediaDirPath.back() != '/')
{
mediaDirPath = mediaDirPath + "/";
}
}
return mediaDirPath;
}
@ -106,20 +116,20 @@ const std::string FileData::getThumbnailPath() const
const char* extList[2] = { ".png", ".jpg" };
std::string tempPath = getMediaDirectory() + mSystemName + "/thumbnails/" + getDisplayName();
// Look for media in the media directory
for(int i = 0; i < 2; i++)
{
// Look for media in the media directory.
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look for local art as well (if configured to do so)
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt"))
{
for(int i = 0; i < 2; i++)
{
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() + "-thumbnail" + extList[i];
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
getDisplayName() + "-thumbnail" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
@ -133,20 +143,20 @@ const std::string FileData::getVideoPath() const
const char* extList[5] = { ".avi", ".mkv", ".mov", ".mp4", ".wmv" };
std::string tempPath = getMediaDirectory() + mSystemName + "/videos/" + getDisplayName();
// Look for media in the media directory
for(int i = 0; i < 5; i++)
{
// Look for media in the media directory.
for (int i = 0; i < 5; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look for local art as well (if configured to do so)
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt"))
{
for(int i = 0; i < 5; i++)
{
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() + "-video" + extList[i];
for (int i = 0; i < 5; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() +
"-video" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
@ -160,20 +170,20 @@ const std::string FileData::getMarqueePath() const
const char* extList[2] = { ".png", ".jpg" };
std::string tempPath = getMediaDirectory() + mSystemName + "/marquees/" + getDisplayName();
// Look for media in the media directory
for(int i = 0; i < 2; i++)
{
// Look for media in the media directory.
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look for local art as well (if configured to do so)
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt"))
{
for(int i = 0; i < 2; i++)
{
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() + "-marquee" + extList[i];
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() +
"-marquee" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
@ -186,31 +196,29 @@ const std::string FileData::getImagePath() const
{
const char* extList[2] = { ".png", ".jpg" };
// Look for mix image (a combination of screenshot, 3D box and marquee) in the media directory
// Look for mix image (a combination of screenshot, 3D box and marquee) in the media directory.
std::string tempPath = getMediaDirectory() + mSystemName + "/miximages/" + getDisplayName();
for(int i = 0; i < 2; i++)
{
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// If no mix image exists, try normal screenshot
// If no mix image exists, try normal screenshot.
tempPath = getMediaDirectory() + mSystemName + "/screenshots/" + getDisplayName();
for(int i = 0; i < 2; i++)
{
for (int i = 0; i < 2; i++) {
std::string mediaPath = tempPath + extList[i];
if (Utils::FileSystem::exists(mediaPath))
return mediaPath;
}
// No media found in the media directory, so look for local art as well (if configured to do so)
if(Settings::getInstance()->getBool("LocalArt"))
{
for(int i = 0; i < 2; i++)
{
std::string localMediaPath = mEnvData->mStartPath + "/images/" + getDisplayName() + "-image" + extList[i];
// No media found in the media directory, so look
// for local art as well (if configured to do so).
if (Settings::getInstance()->getBool("LocalArt")) {
for (int i = 0; i < 2; i++) {
std::string localMediaPath = mEnvData->mStartPath + "/images/" +
getDisplayName() + "-image" + extList[i];
if (Utils::FileSystem::exists(localMediaPath))
return localMediaPath;
}
@ -225,17 +233,14 @@ const std::vector<FileData*>& FileData::getChildrenListToDisplay()
FileFilterIndex* idx = CollectionSystemManager::get()->getSystemToView(mSystem)->getIndex();
if (idx->isFiltered()) {
mFilteredChildren.clear();
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
if (idx->showFile((*it))) {
mFilteredChildren.push_back(*it);
}
}
return mFilteredChildren;
}
else
{
else {
return mChildren;
}
}
@ -245,16 +250,12 @@ std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask, bool d
std::vector<FileData*> out;
FileFilterIndex* idx = mSystem->getIndex();
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
if((*it)->getType() & typeMask)
{
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
if ((*it)->getType() & typeMask) {
if (!displayedOnly || !idx->isFiltered() || idx->showFile(*it))
out.push_back(*it);
}
if((*it)->getChildren().size() > 0)
{
if ((*it)->getChildren().size() > 0) {
std::vector<FileData*> subchildren = (*it)->getFilesRecursive(typeMask, displayedOnly);
out.insert(out.cend(), subchildren.cbegin(), subchildren.cend());
}
@ -271,9 +272,10 @@ const bool FileData::isArcadeAsset()
{
const std::string stem = Utils::FileSystem::getStem(mPath);
return (
(mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) || mSystem->hasPlatformId(PlatformIds::NEOGEO)))
&&
(MameNames::getInstance()->isBios(stem) || MameNames::getInstance()->isDevice(stem))
(mSystem && (mSystem->hasPlatformId(PlatformIds::ARCADE) ||
mSystem->hasPlatformId(PlatformIds::NEOGEO))) &&
(MameNames::getInstance()->isBios(stem) ||
MameNames::getInstance()->isDevice(stem))
);
}
@ -288,8 +290,7 @@ void FileData::addChild(FileData* file)
assert(file->getParent() == NULL);
const std::string key = file->getKey();
if (mChildrenByFilename.find(key) == mChildrenByFilename.cend())
{
if (mChildrenByFilename.find(key) == mChildrenByFilename.cend()) {
mChildrenByFilename[key] = file;
mChildren.push_back(file);
file->mParent = this;
@ -301,10 +302,8 @@ void FileData::removeChild(FileData* file)
assert(mType == FOLDER);
assert(file->getParent() == this);
mChildrenByFilename.erase(file->getKey());
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
if(*it == file)
{
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
if (*it == file) {
file->mParent = NULL;
mChildren.erase(it);
return;
@ -320,8 +319,7 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending)
{
std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
for(auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
if ((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending);
}
@ -330,8 +328,49 @@ void FileData::sort(ComparisonFunction& comparator, bool ascending)
std::reverse(mChildren.begin(), mChildren.end());
}
void FileData::sort(const SortType& type)
void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending)
{
std::vector<FileData*> mChildrenFavorites;
std::vector<FileData*> mChildrenOthers;
for (unsigned int i = 0; i < mChildren.size(); i++) {
if (mChildren[i]->getFavorite())
mChildrenFavorites.push_back(mChildren[i]);
else
mChildrenOthers.push_back(mChildren[i]);
}
// Sort favorite games and the other games separately.
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
for (auto it = mChildrenFavorites.cbegin(); it != mChildrenFavorites.cend(); it++) {
if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, ascending);
}
for (auto it = mChildrenOthers.cbegin(); it != mChildrenOthers.cend(); it++) {
if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, ascending);
}
if (!ascending) {
std::reverse(mChildrenFavorites.begin(), mChildrenFavorites.end());
std::reverse(mChildrenOthers.begin(), mChildrenOthers.end());
}
// Combine the individually sorted favorite games and other games vectors.
mChildren.erase(mChildren.begin(), mChildren.end());
mChildren.reserve(mChildrenFavorites.size() + mChildrenOthers.size());
mChildren.insert(mChildren.end(), mChildrenFavorites.begin(), mChildrenFavorites.end());
mChildren.insert(mChildren.end(), mChildrenOthers.begin(), mChildrenOthers.end());
}
void FileData::sort(const SortType& type, bool mFavoritesOnTop)
{
if (mFavoritesOnTop)
sortFavoritesOnTop(*type.comparisonFunction, type.ascending);
else
sort(*type.comparisonFunction, type.ascending);
}
@ -346,15 +385,13 @@ void FileData::launchGame(Window* window)
std::string command = "";
// Check if there is a launch string override for the game and the corresponding option has been set
if(Settings::getInstance()->getBool("LaunchstringOverride") && !metadata.get("launchstring").empty())
{
// Check if there is a launch string override for the game
// and the corresponding option to use it has been set.
if (Settings::getInstance()->getBool("LaunchstringOverride") &&
!metadata.get("launchstring").empty())
command = metadata.get("launchstring");
}
else
{
command = mEnvData->mLaunchCommand;
}
const std::string rom = Utils::FileSystem::getEscapedPath(getPath());
const std::string basename = Utils::FileSystem::getStem(getPath());
@ -370,24 +407,22 @@ void FileData::launchGame(Window* window)
int exitCode = runSystemCommand(command);
if (exitCode != 0)
{
LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!";
}
Scripting::fireEvent("game-end");
// window->init();
VolumeControl::getInstance()->init();
window->normalizeNextUpdate();
//update number of times the game has been launched
// Update number of times the game has been launched.
FileData* gameToUpdate = getSourceFileData();
int timesPlayed = gameToUpdate->metadata.getInt("playcount") + 1;
gameToUpdate->metadata.set("playcount", std::to_string(static_cast<long long>(timesPlayed)));
//update last played time
// Update last played time.
gameToUpdate->metadata.set("lastplayed", Utils::Time::DateTime(Utils::Time::now()));
CollectionSystemManager::get()->refreshCollectionSystems(gameToUpdate);
@ -395,9 +430,10 @@ void FileData::launchGame(Window* window)
}
CollectionFileData::CollectionFileData(FileData* file, SystemData* system)
: FileData(file->getSourceFileData()->getType(), file->getSourceFileData()->getPath(), file->getSourceFileData()->getSystemEnvData(), system)
: FileData(file->getSourceFileData()->getType(), file->getSourceFileData()->getPath(),
file->getSourceFileData()->getSystemEnvData(), system)
{
// we use this constructor to create a clone of the filedata, and change its system
// We use this constructor to create a clone of the filedata, and change its system.
mSourceFileData = file->getSourceFileData();
refreshMetadata();
mParent = NULL;
@ -407,7 +443,7 @@ CollectionFileData::CollectionFileData(FileData* file, SystemData* system)
CollectionFileData::~CollectionFileData()
{
// need to remove collection file data at the collection object destructor
// Need to remove collection file data at the collection object destructor.
if (mParent)
mParent->removeChild(this);
mParent = NULL;
@ -431,28 +467,28 @@ void CollectionFileData::refreshMetadata()
const std::string& CollectionFileData::getName()
{
if (mDirty) {
mCollectionFileName = Utils::String::removeParenthesis(mSourceFileData->metadata.get("name"));
mCollectionFileName += " [" + Utils::String::toUpper(mSourceFileData->getSystem()->getName()) + "]";
mCollectionFileName =
Utils::String::removeParenthesis(mSourceFileData->metadata.get("name"));
mCollectionFileName +=
" [" + Utils::String::toUpper(mSourceFileData->getSystem()->getName()) + "]";
mDirty = false;
}
if (Settings::getInstance()->getBool("CollectionShowSystemInfo"))
return mCollectionFileName;
return mSourceFileData->metadata.get("name");
}
// returns Sort Type based on a string description
// Return sort type based on a string description.
FileData::SortType getSortTypeFromString(std::string desc) {
std::vector<FileData::SortType> SortTypes = FileSorts::SortTypes;
// find it
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
{
// Find it
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) {
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
if (sort.description == desc)
{
return sort;
}
}
// if not found default to name, ascending
// If no type found then default to "filename, ascending".
return FileSorts::SortTypes.at(0);
}

View file

@ -1,3 +1,11 @@
//
// FileData.h
//
// Provides game file data structures and functions to access and sort this information.
// Also provides functions to look up paths to media files and for launching games
// (launching initiated by the ViewController).
//
#pragma once
#ifndef ES_APP_FILE_DATA_H
#define ES_APP_FILE_DATA_H
@ -10,15 +18,13 @@ class SystemData;
class Window;
struct SystemEnvironmentData;
enum FileType
{
enum FileType {
GAME = 1, // Cannot have children.
FOLDER = 2,
PLACEHOLDER = 3
};
enum FileChangeType
{
enum FileChangeType {
FILE_ADDED,
FILE_METADATA_CHANGED,
FILE_REMOVED,
@ -33,7 +39,11 @@ FileType stringToFileType(const char* str);
class FileData
{
public:
FileData(FileType type, const std::string& path, SystemEnvironmentData* envData, SystemData* system);
FileData(FileType type,
const std::string& path,
SystemEnvironmentData* envData,
SystemData* system);
virtual ~FileData();
virtual const std::string& getName();
@ -42,7 +52,8 @@ public:
inline FileType getType() const { return mType; }
inline const std::string& getPath() const { return mPath; }
inline FileData* getParent() const { return mParent; }
inline const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const { return mChildrenByFilename; }
inline const std::unordered_map<std::string, FileData*>& getChildrenByFilename() const
{ return mChildrenByFilename; }
inline const std::vector<FileData*>& getChildren() const { return mChildren; }
inline SystemData* getSystem() const { return mSystem; }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
@ -53,7 +64,8 @@ public:
virtual const std::string getImagePath() const;
const std::vector<FileData*>& getChildrenListToDisplay();
std::vector<FileData*> getFilesRecursive(unsigned int typeMask, bool displayedOnly = false) const;
std::vector<FileData*> getFilesRecursive(unsigned int typeMask,
bool displayedOnly = false) const;
void addChild(FileData* file); // Error if mType != FOLDER
void removeChild(FileData* file); //Error if mType != FOLDER
@ -69,33 +81,42 @@ 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
// (will attempt to perform MAME name translation).
std::string getDisplayName() const;
// As above, but also remove parenthesis
// As above, but also remove parenthesis.
std::string getCleanName() const;
void launchGame(Window* window);
typedef bool ComparisonFunction(const FileData* a, const FileData* b);
struct SortType
{
struct SortType {
ComparisonFunction* comparisonFunction;
bool ascending;
std::string description;
SortType(ComparisonFunction* sortFunction, bool sortAscending, const std::string & sortDescription)
: comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {}
SortType(ComparisonFunction* sortFunction,
bool sortAscending,
const std::string& sortDescription)
: comparisonFunction(sortFunction),
ascending(sortAscending),
description(sortDescription) {}
};
void sort(ComparisonFunction& comparator, bool ascending = true);
void sort(const SortType& type);
void sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending = true);
void sort(const SortType& type, bool mFavoritesOnTop = false);
MetaDataList metadata;
inline void setSortTypeString(std::string typestring) { mSortTypeString = typestring; }
inline std::string getSortTypeString() { return mSortTypeString; }
protected:
FileData* mSourceFileData;
FileData* mParent;
std::string mSystemName;
std::string mSortTypeString = "";
private:
FileType mType;
@ -117,7 +138,7 @@ public:
FileData* getSourceFileData();
std::string getKey();
private:
// needs to be updated when metadata changes
// Needs to be updated when metadata changes.
std::string mCollectionFileName;
bool mDirty;
};

View file

@ -1,3 +1,9 @@
//
// Gamelist.cpp
//
// Parses and updates the gamelist.xml files.
//
#include "Gamelist.h"
#include <chrono>
@ -12,24 +18,24 @@
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
{
// first, verify that path is within the system's root folder
// First, verify that path is within the system's root folder.
FileData* root = system->getRootFolder();
bool contains = false;
std::string relative = Utils::FileSystem::removeCommonPath(path, root->getPath(), contains);
if(!contains)
{
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" << system->getStartPath() << "\"";
return NULL;
if (!contains) {
LOG(LogError) << "File path \"" << path << "\" is outside system path \"" <<
system->getStartPath() << "\"";
return nullptr;
}
Utils::FileSystem::stringList pathList = Utils::FileSystem::getPathList(relative);
auto path_it = pathList.begin();
FileData* treeNode = root;
bool found = false;
while(path_it != pathList.end())
{
const std::unordered_map<std::string, FileData*>& children = treeNode->getChildrenByFilename();
while (path_it != pathList.end()) {
const std::unordered_map<std::string, FileData*>& children =
treeNode->getChildrenByFilename();
std::string key = *path_it;
found = children.find(key) != children.cend();
@ -37,40 +43,35 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
treeNode = children.at(key);
}
// this is the end
if(path_it == --pathList.end())
{
// This is the end
if (path_it == --pathList.end()) {
if (found)
return treeNode;
if(type == FOLDER)
{
LOG(LogWarning) << "gameList: folder doesn't already exist, won't create";
return NULL;
if (type == FOLDER) {
LOG(LogWarning) << "Gamelist: folder doesn't exist, won't create";
return nullptr;
}
FileData* file = new FileData(type, path, system->getSystemEnvData(), system);
// skipping arcade assets from gamelist
// Skipping arcade assets from gamelist.
if (!file->isArcadeAsset())
{
treeNode->addChild(file);
}
return file;
}
if(!found)
{
// don't create folders unless it's leading up to a game
// if type is a folder it's gonna be empty, so don't bother
if(type == FOLDER)
{
LOG(LogWarning) << "gameList: folder doesn't already exist, won't create";
return NULL;
if (!found) {
// Don't create folders unless they're including any games.
// If the type is FOLDER it's going to be empty, so don't bother.
if (type == FOLDER) {
LOG(LogWarning) << "Gamelist: folder doesn't exist, won't create";
return nullptr;
}
// create missing folder
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath()) + "/" + *path_it, system->getSystemEnvData(), system);
// Create missing folder.
FileData* folder = new FileData(FOLDER, Utils::FileSystem::getStem(treeNode->getPath())
+ "/" + *path_it, system->getSystemEnvData(), system);
treeNode->addChild(folder);
treeNode = folder;
}
@ -78,7 +79,7 @@ FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType
path_it++;
}
return NULL;
return nullptr;
}
void parseGamelist(SystemData* system)
@ -94,15 +95,14 @@ void parseGamelist(SystemData* system)
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(xmlpath.c_str());
if(!result)
{
LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description();
if (!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlpath <<
"\"!\n " <<result.description();
return;
}
pugi::xml_node root = doc.child("gameList");
if(!root)
{
if (!root) {
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlpath << "\"!";
return;
}
@ -111,32 +111,31 @@ void parseGamelist(SystemData* system)
const char* tagList[2] = { "game", "folder" };
FileType typeList[2] = { GAME, FOLDER };
for(int i = 0; i < 2; i++)
{
for (int i = 0; i < 2; i++) {
const char* tag = tagList[i];
FileType type = typeList[i];
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
{
const std::string path = Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(), relativeTo, false);
for (pugi::xml_node fileNode = root.child(tag); fileNode; fileNode =
fileNode.next_sibling(tag)) {
const std::string path =
Utils::FileSystem::resolveRelativePath(fileNode.child("path").text().get(),
relativeTo, false);
if(!trustGamelist && !Utils::FileSystem::exists(path))
{
if (!trustGamelist && !Utils::FileSystem::exists(path)) {
LOG(LogWarning) << "File \"" << path << "\" does not exist! Ignoring.";
continue;
}
FileData* file = findOrCreateFile(system, path, type);
if(!file)
{
LOG(LogError) << "Error finding/creating FileData for \"" << path << "\", skipping.";
if (!file) {
LOG(LogError) << "Error finding/creating FileData for \"" <<
path << "\", skipping.";
continue;
}
else if(!file->isArcadeAsset())
{
else if (!file->isArcadeAsset()) {
std::string defaultName = file->metadata.get("name");
file->metadata = MetaDataList::createFromXML(GAME_METADATA, fileNode, relativeTo);
//make sure name gets set if one didn't exist
// Make sure a name gets set if one doesn't exist.
if (file->metadata.get("name").empty())
file->metadata.set("name", defaultName);
@ -146,36 +145,41 @@ void parseGamelist(SystemData* system)
}
}
void addFileDataNode(pugi::xml_node& parent, const FileData* file, const char* tag, SystemData* system)
void addFileDataNode(pugi::xml_node& parent, const FileData* file,
const char* tag, SystemData* system)
{
//create game and add to parent node
// Create game and add to parent node.
pugi::xml_node newNode = parent.append_child(tag);
//write metadata
// Write metadata.
file->metadata.appendToXML(newNode, true, system->getStartPath());
if(newNode.children().begin() == newNode.child("name") //first element is name
&& ++newNode.children().begin() == newNode.children().end() //theres only one element
&& newNode.child("name").text().get() == file->getDisplayName()) //the name is the default
{
//if the only info is the default name, don't bother with this node
//delete it and ultimately do nothing
parent.remove_child(newNode);
}else{
//there's something useful in there so we'll keep the node, add the path
// First element is "name", there's only one element and the name is the default.
if (newNode.children().begin() == newNode.child("name") &&
++newNode.children().begin() == newNode.children().end() &&
newNode.child("name").text().get() == file->getDisplayName()) {
// try and make the path relative if we can so things still work if we change the rom folder location in the future
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file->getPath(), system->getStartPath(), false).c_str());
// If the only info is the default name, don't bother
// with this node, delete it and ultimately do nothing.
parent.remove_child(newNode);
}
else {
// There's something useful in there so we'll keep the node, add the path.
// Try and make the path relative if we can so things still
// work if we change the ROM folder location in the future.
newNode.prepend_child("path").text().set(Utils::FileSystem::createRelativePath(file->
getPath(), system->getStartPath(), false).c_str());
}
}
void updateGamelist(SystemData* system)
{
//We do this by reading the XML again, adding changes and then writing it back,
//because there might be information missing in our systemdata which would then miss in the new XML.
//We have the complete information for every game though, so we can simply remove a game
//we already have in the system from the XML, and then add it back from its GameData information...
// We do this by reading the XML again, adding changes and then writing them back,
// because there might be information missing in our systemdata which we would otherwise
// miss in the new XML file. We have the complete information for every game though, so
// we can simply remove a game we already have in the system from the XML, and then add
// it back from its GameData information...
if (Settings::getInstance()->getBool("IgnoreGamelist"))
return;
@ -183,91 +187,95 @@ void updateGamelist(SystemData* system)
pugi::xml_node root;
std::string xmlReadPath = system->getGamelistPath(false);
if(Utils::FileSystem::exists(xmlReadPath))
{
//parse an existing file first
if (Utils::FileSystem::exists(xmlReadPath)) {
// Parse an existing file first.
pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str());
if(!result)
{
LOG(LogError) << "Error parsing XML file \"" << xmlReadPath << "\"!\n " << result.description();
if (!result) {
LOG(LogError) << "Error parsing XML file \"" << xmlReadPath << "\"!\n " <<
result.description();
return;
}
root = doc.child("gameList");
if(!root)
{
LOG(LogError) << "Could not find <gameList> node in gamelist \"" << xmlReadPath << "\"!";
if (!root) {
LOG(LogError) << "Could not find <gameList> node in gamelist \"" <<
xmlReadPath << "\"!";
return;
}
}else{
//set up an empty gamelist to append to
}
else {
// Set up an empty gamelist to append to.
root = doc.append_child("gameList");
}
//now we have all the information from the XML. now iterate through all our games and add information from there
// Now we have all the information from the XML file, so iterate
// through all our games and add the information from there.
FileData* rootFolder = system->getRootFolder();
if (rootFolder != nullptr)
{
if (rootFolder != nullptr) {
int numUpdated = 0;
//get only files, no folders
// Get only files, no folders.
std::vector<FileData*> files = rootFolder->getFilesRecursive(GAME | FOLDER);
//iterate through all files, checking if they're already in the XML
for(std::vector<FileData*>::const_iterator fit = files.cbegin(); fit != files.cend(); ++fit)
{
// Iterate through all files, checking if they're already in the XML file.
for (std::vector<FileData*>::const_iterator fit = files.cbegin();
fit != files.cend(); ++fit) {
const char* tag = ((*fit)->getType() == GAME) ? "game" : "folder";
// do not touch if it wasn't changed anyway
// Do not touch if it wasn't changed.
if (!(*fit)->metadata.wasChanged())
continue;
// check if the file already exists in the XML
// if it does, remove it before adding
for(pugi::xml_node fileNode = root.child(tag); fileNode; fileNode = fileNode.next_sibling(tag))
{
// Check if the file already exists in the XML file.
// If it does, remove the entry before adding it back.
for (pugi::xml_node fileNode = root.child(tag); fileNode;
fileNode = fileNode.next_sibling(tag)) {
pugi::xml_node pathNode = fileNode.child("path");
if(!pathNode)
{
if (!pathNode) {
LOG(LogError) << "<" << tag << "> node contains no <path> child!";
continue;
}
std::string nodePath = Utils::FileSystem::getCanonicalPath(Utils::FileSystem::resolveRelativePath(pathNode.text().get(), system->getStartPath(), true));
std::string nodePath =Utils::FileSystem::getCanonicalPath(
Utils::FileSystem::resolveRelativePath(pathNode.text().get(),
system->getStartPath(), true));
std::string gamePath = Utils::FileSystem::getCanonicalPath((*fit)->getPath());
if(nodePath == gamePath)
{
// found it
if (nodePath == gamePath) {
// Found it
root.remove_child(fileNode);
break;
}
}
// it was either removed or never existed to begin with; either way, we can add it now
// It was either removed or never existed to begin with.
// Either way, we can add it now.
addFileDataNode(root, *fit, tag, system);
++numUpdated;
}
//now write the file
// Now write the file.
if (numUpdated > 0) {
const auto startTs = std::chrono::system_clock::now();
//make sure the folders leading up to this path exist (or the write will fail)
// Make sure the folders leading up to this path exist (or the write will fail).
std::string xmlWritePath(system->getGamelistPath(true));
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
LOG(LogInfo) << "Added/Updated " << numUpdated << " entities in '" << xmlReadPath << "'";
LOG(LogInfo) << "Added/Updated " << numUpdated <<
" entities in '" << xmlReadPath << "'";
if (!doc.save_file(xmlWritePath.c_str())) {
LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << system->getName() << ")!";
LOG(LogError) << "Error saving gamelist.xml to \"" <<
xmlWritePath << "\" (for system " << system->getName() << ")!";
}
const auto endTs = std::chrono::system_clock::now();
LOG(LogInfo) << "Saved gamelist.xml for system \"" << system->getName() << "\" in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms";
LOG(LogInfo) << "Saved gamelist.xml for system \"" << system->getName() << "\" in " <<
std::chrono::duration_cast<std::chrono::milliseconds>
(endTs - startTs).count() << " ms";
}
}else{
}
else {
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!";
}
}

View file

@ -1,3 +1,9 @@
//
// Gamelist.h
//
// Parses and updates the gamelist.xml files.
//
#pragma once
#ifndef ES_APP_GAME_LIST_H
#define ES_APP_GAME_LIST_H

View file

@ -1,3 +1,10 @@
//
// MetaData.cpp
//
// Static data for default metadata values as well as functions
// to read and write metadata from the gamelist files.
//
#include "MetaData.h"
#include "utils/FileSystemUtil.h"
@ -24,7 +31,8 @@ MetaDataDecl gameDecls[] = {
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"}
};
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls +
sizeof(gameDecls) / sizeof(gameDecls[0]));
MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false, "name", "enter game name"},
@ -37,12 +45,12 @@ MetaDataDecl folderDecls[] = {
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"}
};
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls +
sizeof(folderDecls) / sizeof(folderDecls[0]));
const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type)
{
switch(type)
{
switch(type) {
case GAME_METADATA:
return gameMDD;
case FOLDER_METADATA:
@ -53,8 +61,6 @@ const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type)
return gameMDD;
}
MetaDataList::MetaDataList(MetaDataListType type)
: mType(type), mWasChanged(false)
{
@ -63,26 +69,23 @@ MetaDataList::MetaDataList(MetaDataListType type)
set(iter->key, iter->defaultValue);
}
MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node& node, const std::string& relativeTo)
MetaDataList MetaDataList::createFromXML(MetaDataListType type,
pugi::xml_node& node, const std::string& relativeTo)
{
MetaDataList mdl(type);
const std::vector<MetaDataDecl>& mdd = mdl.getMDD();
for(auto iter = mdd.cbegin(); iter != mdd.cend(); iter++)
{
for (auto iter = mdd.cbegin(); iter != mdd.cend(); iter++) {
pugi::xml_node md = node.child(iter->key.c_str());
if(md)
{
// if it's a path, resolve relative paths
if (md) {
// If it's a path, resolve relative paths.
std::string value = md.text().get();
if (iter->type == MD_PATH)
{
value = Utils::FileSystem::resolveRelativePath(value, relativeTo, true);
}
mdl.set(iter->key, value);
}else{
}
else {
mdl.set(iter->key, iter->defaultValue);
}
}
@ -90,21 +93,20 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node&
return mdl;
}
void MetaDataList::appendToXML(pugi::xml_node& parent, bool ignoreDefaults, const std::string& relativeTo) const
void MetaDataList::appendToXML(pugi::xml_node& parent, bool ignoreDefaults,
const std::string& relativeTo) const
{
const std::vector<MetaDataDecl>& mdd = getMDD();
for(auto mddIter = mdd.cbegin(); mddIter != mdd.cend(); mddIter++)
{
for (auto mddIter = mdd.cbegin(); mddIter != mdd.cend(); mddIter++) {
auto mapIter = mMap.find(mddIter->key);
if(mapIter != mMap.cend())
{
// we have this value!
// if it's just the default (and we ignore defaults), don't write it
if (mapIter != mMap.cend()) {
// We have this value!
// If it's just the default (and we ignore defaults), don't write it.
if (ignoreDefaults && mapIter->second == mddIter->defaultValue)
continue;
// try and make paths relative if we can
// Try and make paths relative if we can.
std::string value = mapIter->second;
if (mddIter->type == MD_PATH)
value = Utils::FileSystem::createRelativePath(value, relativeTo, true);
@ -122,7 +124,12 @@ void MetaDataList::set(const std::string& key, const std::string& value)
const std::string& MetaDataList::get(const std::string& key) const
{
// Check that the key actually exists, otherwise return empty string.
if (mMap.count(key) > 0)
return mMap.at(key);
else
return mNoResult;
}
int MetaDataList::getInt(const std::string& key) const

View file

@ -1,3 +1,10 @@
//
// MetaData.h
//
// Static data for default metadata values as well as functions
// to read and write metadata from the gamelist files.
//
#pragma once
#ifndef ES_APP_META_DATA_H
#define ES_APP_META_DATA_H
@ -7,35 +14,35 @@
namespace pugi { class xml_node; }
enum MetaDataType
{
//generic types
enum MetaDataType {
// Generic types
MD_STRING,
MD_INT,
MD_FLOAT,
MD_BOOL,
//specialized types
// Specialized types
MD_MULTILINE_STRING,
MD_LAUNCHSTRING,
MD_PATH,
MD_RATING,
MD_DATE,
MD_TIME //used for lastplayed
MD_TIME // Used for lastplayed
};
struct MetaDataDecl
{
struct MetaDataDecl {
std::string key;
MetaDataType type;
std::string defaultValue;
bool isStatistic; //if true, ignore values for this metadata
std::string displayName; // displayed as this in editors
std::string displayPrompt; // phrase displayed in editors when prompted to enter value (currently only for strings)
// If true, ignore values for this metadata.
bool isStatistic;
// Displayed as this in editors.
std::string displayName;
// Phrase displayed in editors when prompted to enter value (currently only for strings).
std::string displayPrompt;
};
enum MetaDataListType
{
enum MetaDataListType {
GAME_METADATA,
FOLDER_METADATA
};
@ -45,8 +52,10 @@ const std::vector<MetaDataDecl>& getMDDByType(MetaDataListType type);
class MetaDataList
{
public:
static MetaDataList createFromXML(MetaDataListType type, pugi::xml_node& node, const std::string& relativeTo);
void appendToXML(pugi::xml_node& parent, bool ignoreDefaults, const std::string& relativeTo) const;
static MetaDataList createFromXML(MetaDataListType type,
pugi::xml_node& node, const std::string& relativeTo);
void appendToXML(pugi::xml_node& parent, bool ignoreDefaults,
const std::string& relativeTo) const;
MetaDataList(MetaDataListType type);
@ -65,6 +74,7 @@ public:
private:
MetaDataListType mType;
std::map<std::string, std::string> mMap;
std::string mNoResult = "";
bool mWasChanged;
};

View file

@ -1,3 +1,12 @@
//
// SystemData.cpp
//
// Provides data structures for the game systems and populates and indexes them based
// on the configuration in es_systems.cfg as well as the presence of game ROM files.
// Also provides functions to read and write to the gamelist files and to handle theme
// loading.
//
#include "SystemData.h"
#include "utils/FileSystemUtil.h"
@ -18,14 +27,23 @@
std::vector<SystemData*> SystemData::sSystemVector;
SystemData::SystemData(const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, bool CollectionSystem) :
mName(name), mFullName(fullName), mEnvData(envData), mThemeFolder(themeFolder), mIsCollectionSystem(CollectionSystem), mIsGameSystem(true)
SystemData::SystemData(
const std::string& name,
const std::string& fullName,
SystemEnvironmentData* envData,
const std::string& themeFolder,
bool CollectionSystem)
: mName(name),
mFullName(fullName),
mEnvData(envData),
mThemeFolder(themeFolder),
mIsCollectionSystem(CollectionSystem),
mIsGameSystem(true)
{
mFilterIndex = new FileFilterIndex();
// if it's an actual system, initialize it, if not, just create the data structure
if(!CollectionSystem)
{
// If it's an actual system, initialize it, if not, just create the data structure.
if (!CollectionSystem) {
mRootFolder = new FileData(FOLDER, mEnvData->mStartPath, mEnvData, this);
mRootFolder->metadata.set("name", mFullName);
@ -35,14 +53,18 @@ SystemData::SystemData(const std::string& name, const std::string& fullName, Sys
if (!Settings::getInstance()->getBool("IgnoreGamelist"))
parseGamelist(this);
mRootFolder->sort(FileSorts::SortTypes.at(0));
setupSystemSortType(mRootFolder);
mRootFolder->sort(getSortTypeFromString(mRootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavoritesFirst"));
indexAllGameFilters(mRootFolder);
}
else
{
// virtual systems are updated afterwards, we're just creating the data structure
else {
// Virtual systems are updated afterwards by CollectionSystemManager.
// We're just creating the data structure here.
mRootFolder = new FileData(FOLDER, "" + name, mEnvData, this);
setupSystemSortType(mRootFolder);
}
setIsGameSystemStatus();
loadTheme();
@ -59,27 +81,25 @@ SystemData::~SystemData()
void SystemData::setIsGameSystemStatus()
{
// we exclude non-game systems from specific operations (i.e. the "RetroPie" system, at least)
// if/when there are more in the future, maybe this can be a more complex method, with a proper list
// but for now a simple string comparison is more performant
// We exclude non-game systems from specific operations (i.e. the "RetroPie" system, at least).
// If/when there are more in the future, maybe this can be a more complex method, with a proper
// list but for now a simple string comparison is more performant.
mIsGameSystem = (mName != "retropie");
}
void SystemData::populateFolder(FileData* folder)
{
const std::string& folderPath = folder->getPath();
if(!Utils::FileSystem::isDirectory(folderPath))
{
if (!Utils::FileSystem::isDirectory(folderPath)) {
LOG(LogWarning) << "Error - folder with path \"" << folderPath << "\" is not a directory!";
return;
}
//make sure that this isn't a symlink to a thing we already have
if(Utils::FileSystem::isSymlink(folderPath))
{
//if this symlink resolves to somewhere that's at the beginning of our path, it's gonna recurse
if(folderPath.find(Utils::FileSystem::getCanonicalPath(folderPath)) == 0)
{
// Make sure that this isn't a symlink to an object we already have.
if (Utils::FileSystem::isSymlink(folderPath)) {
// If this symlink resolves to somewhere that's at the beginning of our
// path, it's going to create a recursive loop. Make sure to avoid this.
if (folderPath.find(Utils::FileSystem::getCanonicalPath(folderPath)) == 0) {
LOG(LogWarning) << "Skipping infinitely recursive symlink \"" << folderPath << "\"";
return;
}
@ -90,41 +110,41 @@ void SystemData::populateFolder(FileData* folder)
bool isGame;
bool showHidden = Settings::getInstance()->getBool("ShowHiddenFiles");
Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent(folderPath);
for(Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin(); it != dirContent.cend(); ++it)
{
for (Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin();
it != dirContent.cend(); ++it) {
filePath = *it;
// skip hidden files and folders
// Skip hidden files and folders.
if (!showHidden && Utils::FileSystem::isHidden(filePath))
continue;
//this is a little complicated because we allow a list of extensions to be defined (delimited with a space)
//we first get the extension of the file itself:
// This is a little complicated because we allow a list
// of extensions to be defined (delimited with a space).
// We first get the extension of the file itself:
extension = Utils::FileSystem::getExtension(filePath);
//fyi, folders *can* also match the extension and be added as games - this is mostly just to support higan
//see issue #75: https://github.com/Aloshi/EmulationStation/issues/75
// FYI, folders *can* also match the extension and be added as games.
// This is mostly just to support higan.
// See issue #75: https://github.com/Aloshi/EmulationStation/issues/75
isGame = false;
if(std::find(mEnvData->mSearchExtensions.cbegin(), mEnvData->mSearchExtensions.cend(), extension) != mEnvData->mSearchExtensions.cend())
{
if (std::find(mEnvData->mSearchExtensions.cbegin(), mEnvData->mSearchExtensions.cend(),
extension) != mEnvData->mSearchExtensions.cend()) {
FileData* newGame = new FileData(GAME, filePath, mEnvData, this);
// preventing new arcade assets to be added
if(!newGame->isArcadeAsset())
{
// Prevent new arcade assets from being added.
if (!newGame->isArcadeAsset()) {
folder->addChild(newGame);
isGame = true;
}
}
//add directories that also do not match an extension as folders
if(!isGame && Utils::FileSystem::isDirectory(filePath))
{
// Add directories that also do not match an extension as folders.
if (!isGame && Utils::FileSystem::isDirectory(filePath)) {
FileData* newFolder = new FileData(FOLDER, filePath, mEnvData, this);
populateFolder(newFolder);
//ignore folders that do not contain games
// Ignore folders that do not contain games.
if (newFolder->getChildrenByFilename().size() == 0)
delete newFolder;
else
@ -137,12 +157,17 @@ void SystemData::indexAllGameFilters(const FileData* folder)
{
const std::vector<FileData*>& children = folder->getChildren();
for(std::vector<FileData*>::const_iterator it = children.cbegin(); it != children.cend(); ++it)
{
switch((*it)->getType())
{
case GAME: { mFilterIndex->addToIndex(*it); } break;
case FOLDER: { indexAllGameFilters(*it); } break;
for (std::vector<FileData*>::const_iterator it = children.cbegin();
it != children.cend(); ++it) {
switch ((*it)->getType()) {
case GAME: {
mFilterIndex->addToIndex(*it);
}
break;
case FOLDER: {
indexAllGameFilters(*it);
}
break;
}
}
}
@ -153,8 +178,7 @@ std::vector<std::string> readList(const std::string& str, const char* delims = "
size_t prevOff = str.find_first_not_of(delims, 0);
size_t off = str.find_first_of(delims, prevOff);
while(off != std::string::npos || prevOff != std::string::npos)
{
while (off != std::string::npos || prevOff != std::string::npos) {
ret.push_back(str.substr(prevOff, off - prevOff));
prevOff = str.find_first_not_of(delims, off);
@ -164,7 +188,7 @@ std::vector<std::string> readList(const std::string& str, const char* delims = "
return ret;
}
//creates systems from information located in a config file
// Creates systems from information located in a config file.
bool SystemData::loadConfig()
{
deleteSystems();
@ -173,8 +197,7 @@ bool SystemData::loadConfig()
LOG(LogInfo) << "Loading system config file " << path << "...";
if(!Utils::FileSystem::exists(path))
{
if (!Utils::FileSystem::exists(path)) {
LOG(LogError) << "es_systems.cfg file does not exist!";
writeExampleConfig(getConfigPath(true));
return false;
@ -183,80 +206,78 @@ bool SystemData::loadConfig()
pugi::xml_document doc;
pugi::xml_parse_result res = doc.load_file(path.c_str());
if(!res)
{
if (!res) {
LOG(LogError) << "Could not parse es_systems.cfg file!";
LOG(LogError) << res.description();
return false;
}
//actually read the file
// Actually read the file.
pugi::xml_node systemList = doc.child("systemList");
if(!systemList)
{
if (!systemList) {
LOG(LogError) << "es_systems.cfg is missing the <systemList> tag!";
return false;
}
for(pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system"))
{
for (pugi::xml_node system = systemList.child("system"); system;
system = system.next_sibling("system")) {
std::string name, fullname, path, cmd, themeFolder;
name = system.child("name").text().get();
fullname = system.child("fullname").text().get();
path = system.child("path").text().get();
// convert extensions list from a string into a vector of strings
// Convert extensions list from a string into a vector of strings.
std::vector<std::string> extensions = readList(system.child("extension").text().get());
cmd = system.child("command").text().get();
// platform id list
// Platform ID list
const char* platformList = system.child("platform").text().get();
std::vector<std::string> platformStrs = readList(platformList);
std::vector<PlatformIds::PlatformId> platformIds;
for(auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++)
{
for (auto it = platformStrs.cbegin(); it != platformStrs.cend(); it++) {
const char* str = it->c_str();
PlatformIds::PlatformId platformId = PlatformIds::getPlatformId(str);
if(platformId == PlatformIds::PLATFORM_IGNORE)
{
// when platform is ignore, do not allow other platforms
if (platformId == PlatformIds::PLATFORM_IGNORE) {
// When platform is PLATFORM_IGNORE, do not allow other platforms.
platformIds.clear();
platformIds.push_back(platformId);
break;
}
// if there appears to be an actual platform ID supplied but it didn't match the list, warn
// If there appears to be an actual platform ID supplied
// but it didn't match the list, generate a warning.
if (str != NULL && str[0] != '\0' && platformId == PlatformIds::PLATFORM_UNKNOWN)
LOG(LogWarning) << " Unknown platform for system \"" << name << "\" (platform \"" << str << "\" from list \"" << platformList << "\")";
LOG(LogWarning) << " Unknown platform for system \"" << name << "\" (platform \""
<< str << "\" from list \"" << platformList << "\")";
else if (platformId != PlatformIds::PLATFORM_UNKNOWN)
platformIds.push_back(platformId);
}
// theme folder
// Theme folder.
themeFolder = system.child("theme").text().as_string(name.c_str());
//validate
if(name.empty() || path.empty() || extensions.empty() || cmd.empty())
{
LOG(LogError) << "System \"" << name << "\" is missing name, path, extension, or command!";
// Validate.
if (name.empty() || path.empty() || extensions.empty() || cmd.empty()) {
LOG(LogError) << "System \"" << name <<
"\" is missing name, path, extension, or command!";
continue;
}
//convert path to generic directory seperators
// Convert path to generic directory seperators.
path = Utils::FileSystem::getGenericPath(path);
//expand home symbol if the startpath contains ~
// Expand home symbol if the startpath contains ~
if (path[0] == '~')
{
path.erase(0, 1);
path.insert(0, Utils::FileSystem::getHomePath());
}
//create the system runtime environment data
// Create the system runtime environment data.
SystemEnvironmentData* envData = new SystemEnvironmentData;
envData->mStartPath = path;
envData->mSearchExtensions = extensions;
@ -264,11 +285,11 @@ bool SystemData::loadConfig()
envData->mPlatformIds = platformIds;
SystemData* newSys = new SystemData(name, fullname, envData, themeFolder);
if(newSys->getRootFolder()->getChildrenByFilename().size() == 0)
{
if (newSys->getRootFolder()->getChildrenByFilename().size() == 0) {
LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it.";
delete newSys;
}else{
}
else {
sSystemVector.push_back(newSys);
}
}
@ -326,9 +347,8 @@ void SystemData::writeExampleConfig(const std::string& path)
void SystemData::deleteSystems()
{
for (unsigned int i = 0; i < sSystemVector.size(); i++)
{
delete sSystemVector.at(i);
}
sSystemVector.clear();
}
@ -352,12 +372,13 @@ SystemData* SystemData::getNext() const
{
std::vector<SystemData*>::const_iterator it = getIterator();
// As we are starting in a valid gamelistview, this will
// always succeed, even if we have to come full circle.
do {
it++;
if (it == sSystemVector.cend())
it = sSystemVector.cbegin();
} while (!(*it)->isVisible());
// as we are starting in a valid gamelistview, this will always succeed, even if we have to come full circle.
return *it;
}
@ -366,12 +387,13 @@ SystemData* SystemData::getPrev() const
{
std::vector<SystemData*>::const_reverse_iterator it = getRevIterator();
// As we are starting in a valid gamelistview, this will
// always succeed, even if we have to come full circle.
do {
it++;
if (it == sSystemVector.crend())
it = sSystemVector.crbegin();
} while (!(*it)->isVisible());
// as we are starting in a valid gamelistview, this will always succeed, even if we have to come full circle.
return *it;
}
@ -384,8 +406,10 @@ std::string SystemData::getGamelistPath(bool forWrite) const
if (Utils::FileSystem::exists(filePath))
return filePath;
filePath = Utils::FileSystem::getHomePath() + "/.emulationstation/gamelists/" + mName + "/gamelist.xml";
if(forWrite) // make sure the directory exists if we're going to write to it, or crashes will happen
filePath = Utils::FileSystem::getHomePath() + "/.emulationstation/gamelists/" +
mName + "/gamelist.xml";
// Make sure the directory exists if we're going to write to it, or crashes will happen.
if (forWrite)
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(filePath));
if (forWrite || Utils::FileSystem::exists(filePath))
return filePath;
@ -395,24 +419,25 @@ std::string SystemData::getGamelistPath(bool forWrite) const
std::string SystemData::getThemePath() const
{
// where we check for themes, in order:
// Locations where we check for themes, in the following order:
// 1. [SYSTEM_PATH]/theme.xml
// 2. system theme from currently selected theme set [CURRENT_THEME_PATH]/[SYSTEM]/theme.xml
// 3. default system theme from currently selected theme set [CURRENT_THEME_PATH]/theme.xml
// 2. System theme from currently selected theme set [CURRENT_THEME_PATH]/[SYSTEM]/theme.xml
// 3. Default system theme from currently selected theme set [CURRENT_THEME_PATH]/theme.xml
// first, check game folder
// First, check game folder.
std::string localThemePath = mRootFolder->getPath() + "/theme.xml";
if (Utils::FileSystem::exists(localThemePath))
return localThemePath;
// not in game folder, try system theme in theme sets
// Not in game folder, try system theme in theme sets.
localThemePath = ThemeData::getThemeFromCurrentSet(mThemeFolder);
if (Utils::FileSystem::exists(localThemePath))
return localThemePath;
// not system theme, try default system theme in theme set
localThemePath = Utils::FileSystem::getParent(Utils::FileSystem::getParent(localThemePath)) + "/theme.xml";
// Not system theme, try default system theme in theme set.
localThemePath = Utils::FileSystem::getParent(Utils::FileSystem::getParent(localThemePath)) +
"/theme.xml";
return localThemePath;
}
@ -429,32 +454,27 @@ unsigned int SystemData::getGameCount() const
SystemData* SystemData::getRandomSystem()
{
// this is a bit brute force. It might be more efficient to just to a while (!gameSystem) do random again...
// This is a bit brute force.
// It might be more efficient to just do a while (!gameSystem) do random again...
unsigned int total = 0;
for(auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++)
{
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++) {
if ((*it)->isGameSystem())
total++;
}
// get random number in range
// Get a random number in range.
int target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1));
for (auto it = sSystemVector.cbegin(); it != sSystemVector.cend(); it++)
{
if ((*it)->isGameSystem())
{
if ((*it)->isGameSystem()) {
if (target > 0)
{
target--;
}
else
{
return (*it);
}
}
}
// if we end up here, there is no valid system
// If we end up here, there is no valid system.
return NULL;
}
@ -463,10 +483,12 @@ FileData* SystemData::getRandomGame()
std::vector<FileData*> list = mRootFolder->getFilesRecursive(GAME, true);
unsigned int total = (int)list.size();
int target = 0;
// get random number in range
// Get a random number in range.
if (total == 0)
return NULL;
target = (int)Math::round((std::rand() / (float)RAND_MAX) * (total - 1));
return list.at(target);
}
@ -481,22 +503,21 @@ void SystemData::loadTheme()
std::string path = getThemePath();
if(!Utils::FileSystem::exists(path)) // no theme available for this platform
if (!Utils::FileSystem::exists(path)) // No theme available for this platform.
return;
try
{
// build map with system variables for theme to use,
try {
// Build map with system variables for theme to use.
std::map<std::string, std::string> sysData;
sysData.insert(std::pair<std::string, std::string>("system.name", getName()));
sysData.insert(std::pair<std::string, std::string>("system.theme", getThemeFolder()));
sysData.insert(std::pair<std::string, std::string>("system.fullName", getFullName()));
mTheme->loadFile(sysData, path);
} catch(ThemeException& e)
{
}
catch (ThemeException& e) {
LOG(LogError) << e.what();
mTheme = std::make_shared<ThemeData>(); // reset to empty
mTheme = std::make_shared<ThemeData>(); // Reset to empty.
}
}
@ -504,7 +525,7 @@ void SystemData::writeMetaData() {
if (Settings::getInstance()->getBool("IgnoreGamelist") || mIsCollectionSystem)
return;
//save changed game data back to xml
// Save changed game data back to xml.
updateGamelist(this);
}
@ -514,3 +535,22 @@ void SystemData::onMetaDataSavePoint() {
writeMetaData();
}
void SystemData::setupSystemSortType(FileData* mRootFolder)
{
// If DefaultSortOrder is set to something, check that it is actually a valid value.
if (Settings::getInstance()->getString("DefaultSortOrder") != "") {
for (unsigned int i = 0; i < FileSorts::SortTypes.size(); i++) {
if (FileSorts::SortTypes.at(i).description ==
Settings::getInstance()->getString("DefaultSortOrder")) {
mRootFolder->setSortTypeString(Settings::getInstance()->
getString("DefaultSortOrder"));
break;
}
}
}
// If no valid sort type was defined in the configuration
// file, set sorting to "filename, ascending".
if (mRootFolder->getSortTypeString() == "")
mRootFolder->setSortTypeString(FileSorts::SortTypes.at(0).description);
}

View file

@ -1,3 +1,12 @@
//
// SystemData.h
//
// Provides data structures for the game systems and populates and indexes them based
// on the configuration in es_systems.cfg as well as the presence of game ROM files.
// Also provides functions to read and write to the gamelist files and to handle theme
// loading.
//
#pragma once
#ifndef ES_APP_SYSTEM_DATA_H
#define ES_APP_SYSTEM_DATA_H
@ -23,18 +32,27 @@ struct SystemEnvironmentData
class SystemData
{
public:
SystemData(const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, bool CollectionSystem = false);
SystemData(const std::string& name,
const std::string& fullName,
SystemEnvironmentData* envData,
const std::string& themeFolder,
bool CollectionSystem = false);
~SystemData();
inline FileData* getRootFolder() const { return mRootFolder; };
inline const std::string& getName() const { return mName; }
inline const std::string& getFullName() const { return mFullName; }
inline const std::string& getStartPath() const { return mEnvData->mStartPath; }
inline const std::vector<std::string>& getExtensions() const { return mEnvData->mSearchExtensions; }
inline const std::vector<std::string>& getExtensions() const
{ return mEnvData->mSearchExtensions; }
inline const std::string& getThemeFolder() const { return mThemeFolder; }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const { return mEnvData->mPlatformIds; }
inline bool hasPlatformId(PlatformIds::PlatformId id) { if (!mEnvData) return false; return std::find(mEnvData->mPlatformIds.cbegin(), mEnvData->mPlatformIds.cend(), id) != mEnvData->mPlatformIds.cend(); }
inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const
{ return mEnvData->mPlatformIds; }
inline bool hasPlatformId(PlatformIds::PlatformId id) { if (!mEnvData) return false;
return std::find(mEnvData->mPlatformIds.cbegin(), mEnvData->mPlatformIds.cend(), id)
!= mEnvData->mPlatformIds.cend(); }
inline const std::shared_ptr<ThemeData>& getTheme() const { return mTheme; }
@ -46,14 +64,21 @@ public:
unsigned int getDisplayedGameCount() const;
static void deleteSystems();
static bool loadConfig(); //Load the system config file at getConfigPath(). Returns true if no errors were encountered. An example will be written if the file doesn't exist.
// Load the system config file at getConfigPath().
// Returns true if no errors were encountered.
// An example will be written if the file doesn't exist.
static bool loadConfig();
static void writeExampleConfig(const std::string& path);
static std::string getConfigPath(bool forWrite); // if forWrite, will only return ~/.emulationstation/es_systems.cfg, never /etc/emulationstation/es_systems.cfg
// If forWrite, will only return ~/.emulationstation/es_systems.cfg,
// never /etc/emulationstation/es_systems.cfg.
static std::string getConfigPath(bool forWrite);
static std::vector<SystemData*> sSystemVector;
inline std::vector<SystemData*>::const_iterator getIterator() const { return std::find(sSystemVector.cbegin(), sSystemVector.cend(), this); };
inline std::vector<SystemData*>::const_reverse_iterator getRevIterator() const { return std::find(sSystemVector.crbegin(), sSystemVector.crend(), this); };
inline std::vector<SystemData*>::const_iterator getIterator() const
{ return std::find(sSystemVector.cbegin(), sSystemVector.cend(), this); };
inline std::vector<SystemData*>::const_reverse_iterator getRevIterator() const
{ return std::find(sSystemVector.crbegin(), sSystemVector.crend(), this); };
inline bool isCollection() { return mIsCollectionSystem; };
inline bool isGameSystem() { return mIsGameSystem; };
@ -70,6 +95,8 @@ public:
FileFilterIndex* getIndex() { return mFilterIndex; };
void onMetaDataSavePoint();
void setupSystemSortType(FileData* mRootFolder);
private:
bool mIsCollectionSystem;
bool mIsGameSystem;

View file

@ -71,9 +71,9 @@ void GuiCollectionSystemsOptions::initializeMenu()
bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections);
sortAllSystemsSwitch = std::make_shared<SwitchComponent>(mWindow);
sortAllSystemsSwitch->setState(Settings::getInstance()->getBool("SortAllSystems"));
mMenu.addWithLabel("SORT CUSTOM COLLECTIONS AND SYSTEMS", sortAllSystemsSwitch);
sortFavFirstCustomSwitch = std::make_shared<SwitchComponent>(mWindow);
sortFavFirstCustomSwitch->setState(Settings::getInstance()->getBool("FavFirstCustom"));
mMenu.addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", sortFavFirstCustomSwitch);
toggleSystemNameInCollections = std::make_shared<SwitchComponent>(mWindow);
toggleSystemNameInCollections->setState(Settings::getInstance()->getBool("CollectionShowSystemInfo"));
@ -170,8 +170,8 @@ void GuiCollectionSystemsOptions::applySettings()
std::string prevAuto = Settings::getInstance()->getString("CollectionSystemsAuto");
std::string outCustom = Utils::String::vectorToCommaString(customOptionList->getSelectedObjects());
std::string prevCustom = Settings::getInstance()->getString("CollectionSystemsCustom");
bool outSort = sortAllSystemsSwitch->getState();
bool prevSort = Settings::getInstance()->getBool("SortAllSystems");
bool outSort = sortFavFirstCustomSwitch->getState();
bool prevSort = Settings::getInstance()->getBool("FavFirstCustom");
bool outBundle = bundleCustomCollections->getState();
bool prevBundle = Settings::getInstance()->getBool("UseCustomCollectionsSystem");
bool prevShow = Settings::getInstance()->getBool("CollectionShowSystemInfo");
@ -189,7 +189,7 @@ void GuiCollectionSystemsOptions::updateSettings(std::string newAutoSettings, st
{
Settings::getInstance()->setString("CollectionSystemsAuto", newAutoSettings);
Settings::getInstance()->setString("CollectionSystemsCustom", newCustomSettings);
Settings::getInstance()->setBool("SortAllSystems", sortAllSystemsSwitch->getState());
Settings::getInstance()->setBool("FavFirstCustom", sortFavFirstCustomSwitch->getState());
Settings::getInstance()->setBool("UseCustomCollectionsSystem", bundleCustomCollections->getState());
Settings::getInstance()->setBool("CollectionShowSystemInfo", toggleSystemNameInCollections->getState());
Settings::getInstance()->saveFile();

View file

@ -28,8 +28,8 @@ private:
void exitEditMode();
std::shared_ptr< OptionListComponent<std::string> > autoOptionList;
std::shared_ptr< OptionListComponent<std::string> > customOptionList;
std::shared_ptr<SwitchComponent> sortAllSystemsSwitch;
std::shared_ptr<SwitchComponent> bundleCustomCollections;
std::shared_ptr<SwitchComponent> sortFavFirstCustomSwitch;
std::shared_ptr<SwitchComponent> toggleSystemNameInCollections;
MenuComponent mMenu;
SystemData* mSystem;

View file

@ -1,3 +1,10 @@
//
// GuiGamelistOptions.cpp
//
// Gamelist options menu for the 'Jump to...' quick selector,
// game sorting, game filters, and metadata edit.
//
#include "GuiGamelistOptions.h"
#include "guis/GuiGamelistFilter.h"
@ -12,22 +19,35 @@
#include "SystemData.h"
#include "Sound.h"
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false)
GuiGamelistOptions::GuiGamelistOptions(
Window* window,
SystemData* system)
: GuiComponent(window),
mSystem(system),
mMenu(window, "OPTIONS"),
fromPlaceholder(false),
mFiltersChanged(false)
{
addChild(&mMenu);
// check it's not a placeholder folder - if it is, only show "Filter Options"
// Check that it's not a placeholder folder - if it is, only show "Filter Options".
FileData* file = getGamelist()->getCursor();
fromPlaceholder = file->isPlaceHolder();
ComponentListRow row;
// Read the applicable favorite sorting setting depending on whether the
// system is a custom collection or not.
if (CollectionSystemManager::get()->getIsCustomCollection(file->getSystem()))
mFavoritesSorting = Settings::getInstance()->getBool("FavFirstCustom");
else
mFavoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (!fromPlaceholder) {
// jump to letter
// Jump to letter.
row.elements.clear();
// define supported character range
// this range includes all numbers, capital letters, and most reasonable symbols
// Define supported character range.
// This range includes all numbers, capital letters, and most reasonable symbols.
char startChar = '!';
char endChar = '_';
@ -37,18 +57,12 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
mJumpToLetterList = std::make_shared<LetterList>(mWindow, "JUMP TO...", false);
if(Settings::getInstance()->getBool("FavoritesFirst") && system->getName() != "favorites" && system->getName() != "recent")
{
// set firstFavorite to the numerical entry of the first favorite game in the list
// if no favorites are found set it to -1
findFirstFavorite();
// if the currently selected game is a favorite, set curChar to 0 so we don't get two current positions
if(getGamelist()->getCursor()->getFavorite())
curChar = 0;
if (firstFavorite != -1)
{
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
@ -56,117 +70,147 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
}
}
for (char c = startChar; c <= endChar; c++)
{
// check if c is a valid first letter in current list
const std::vector<FileData*>& files = getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
for (auto file : files)
{
for (char c = startChar; c <= endChar; c++) {
// Check if c is a valid first letter in the current list.
const std::vector<FileData*>& 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 (firstFavorite != -1 && file->getFavorite())
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<TextComponent>(mWindow, "JUMP TO...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(
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)
{
if (config->isMappedTo("a", input) && input.value) {
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
{
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
jumpToFirstFavorite();
return true;
}
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
jumpToFirstRow();
else
jumpToLetter();
return true;
}
else if(mJumpToLetterList->input(config, input))
{
else if (mJumpToLetterList->input(config, input)) {
return true;
}
return false;
};
if (system->getName() != "recent")
mMenu.addRow(row);
// sort list by
// Sort list by selected sort type (persistent throughout the program session).
mListSort = std::make_shared<SortList>(mWindow, "SORT GAMES BY", false);
for(unsigned int i = 0; i < FileSorts::SortTypes.size(); i++)
{
const FileData::SortType& sort = FileSorts::SortTypes.at(i);
mListSort->add(sort.description, &sort, i == 0); // TODO - actually make the sort type persistent
}
FileData* 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);
}
// show filtered menu
if(!Settings::getInstance()->getBool("ForceDisableFilters"))
{
// Show filtered menu.
if (system->getName() != "recent" && !Settings::getInstance()->getBool("ForceDisableFilters")) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(
mWindow, "FILTER GAMELIST", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openGamelistFilter, this));
mMenu.addRow(row);
}
std::map<std::string, CollectionSystemData> customCollections = CollectionSystemManager::get()->getCustomCollectionSystems();
std::map<std::string, CollectionSystemData> customCollections =
CollectionSystemManager::get()->getCustomCollectionSystems();
if (UIModeController::getInstance()->isUIModeFull() &&
((customCollections.find(system->getName()) != customCollections.cend() && CollectionSystemManager::get()->getEditingCollection() != system->getName()) ||
CollectionSystemManager::get()->getCustomCollectionsBundle()->getName() == system->getName()))
{
((customCollections.find(system->getName()) != customCollections.cend() &&
CollectionSystemManager::get()->getEditingCollection() != system->getName()) ||
CollectionSystemManager::get()->getCustomCollectionsBundle()->getName() ==
system->getName())) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "ADD/REMOVE GAMES TO THIS GAME COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(
mWindow, "ADD/REMOVE GAMES TO THIS GAME COLLECTION", Font::get(FONT_SIZE_MEDIUM),
0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::startEditMode, this));
mMenu.addRow(row);
}
if(UIModeController::getInstance()->isUIModeFull() && CollectionSystemManager::get()->isEditing())
{
if (UIModeController::getInstance()->isUIModeFull() &&
CollectionSystemManager::get()->isEditing()) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "FINISH EDITING '" + Utils::String::toUpper(CollectionSystemManager::get()->getEditingCollection()) + "' COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(
mWindow, "FINISH EDITING '" + Utils::String::toUpper(
CollectionSystemManager::get()->getEditingCollection()) +
"' COLLECTION",Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::exitEditMode, this));
mMenu.addRow(row);
}
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder && !(mSystem->isCollection() && file->getType() == FOLDER))
{
if (UIModeController::getInstance()->isUIModeFull() && !fromPlaceholder &&
!(mSystem->isCollection() && file->getType() == FOLDER)) {
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(mWindow,
"EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(makeArrow(mWindow), false);
row.makeAcceptInputHandler(std::bind(&GuiGamelistOptions::openMetaDataEd, this));
mMenu.addRow(row);
}
// center the menu
// Center the menu.
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() - mMenu.getSize().y()) / 2);
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() -
mMenu.getSize().y()) / 2);
}
GuiGamelistOptions::~GuiGamelistOptions()
{
// apply sort
if (!fromPlaceholder) {
FileData* root = mSystem->getRootFolder();
root->sort(*mListSort->getSelected()); // will also recursively sort children
// notify that the root folder was sorted
// If a new sorting type was selected, then sort and update mSortTypeString for the system.
if ((*mListSort->getSelected()).description != root->getSortTypeString()) {
navigationsounds.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.
getGamelist()->onFileChanged(root, FILE_SORTED);
}
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
}
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);
}
}
@ -181,21 +225,17 @@ void GuiGamelistOptions::openGamelistFilter()
void GuiGamelistOptions::startEditMode()
{
std::string editingSystem = mSystem->getName();
// need to check if we're editing the collections bundle, as we will want to edit the selected collection within
if(editingSystem == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName())
{
// Need to check if we're editing the collections bundle,
// as we will want to edit the selected collection within.
if (editingSystem == CollectionSystemManager::get()->getCustomCollectionsBundle()->getName()) {
FileData* file = getGamelist()->getCursor();
// do we have the cursor on a specific collection?
// Do we have the cursor on a specific collection?.
if (file->getType() == FOLDER)
{
editingSystem = file->getName();
}
else
{
// we are inside a specific collection. We want to edit that one.
// We are inside a specific collection. We want to edit that one.
editingSystem = file->getSystem()->getName();
}
}
CollectionSystemManager::get()->setEditMode(editingSystem);
delete this;
}
@ -208,8 +248,8 @@ void GuiGamelistOptions::exitEditMode()
void GuiGamelistOptions::openMetaDataEd()
{
// open metadata editor
// get the FileData that hosts the original metadata
// Open metadata editor.
// Get the FileData that holds the original metadata.
FileData* file = getGamelist()->getCursor()->getSourceFileData();
ScraperSearchParams p;
p.game = file;
@ -217,106 +257,63 @@ void GuiGamelistOptions::openMetaDataEd()
std::function<void()> deleteBtnFunc;
if (file->getType() == FOLDER)
{
if (file->getType() == FOLDER) {
deleteBtnFunc = NULL;
}
else
{
else {
deleteBtnFunc = [this, file] {
CollectionSystemManager::get()->deleteCollectionFiles(file);
ViewController::get()->getGameListView(file->getSystem()).get()->remove(file, true);
};
}
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p, Utils::FileSystem::getFileName(file->getPath()),
std::bind(&IGameListView::onFileChanged, ViewController::get()->getGameListView(file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
mWindow->pushGui(new GuiMetaDataEd(mWindow, &file->metadata, file->metadata.getMDD(), p,
Utils::FileSystem::getFileName(file->getPath()), std::bind(
&IGameListView::onFileChanged, ViewController::get()->getGameListView(
file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc));
}
void GuiGamelistOptions::jumpToLetter()
{
char letter = mJumpToLetterList->getSelected()[0];
IGameListView* gamelist = getGamelist();
// this is a really shitty way to get a list of files
const std::vector<FileData*>& files = gamelist->getCursor()->getParent()->getChildrenListToDisplay();
// Get first row of the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()->
getParent()->getChildrenListToDisplay();
long min = 0;
long max = (long)files.size() - 1;
long mid = 0;
while(max >= min)
{
mid = ((max - min) / 2) + min;
// game somehow has no first character to check
if(files.at(mid)->getName().empty())
continue;
char checkLetter = (char)toupper(files.at(mid)->getSortName()[0]);
if(checkLetter < letter)
min = mid + 1;
else if(checkLetter > letter || (mid > 0 && (letter == toupper(files.at(mid - 1)->getSortName()[0]))))
max = mid - 1;
else
{
// exact match found
// step through games to exclude favorites
if (firstFavorite != -1)
{
while(files[mid]->getFavorite())
mid++;
}
for (unsigned int i = 0; i < files.size(); i++) {
if (mFavoritesSorting && mSystem->getName() != "favorites") {
if ((char)toupper(files.at(i)->getSortName()[0]) ==
letter && !files.at(i)->getFavorite()) {
getGamelist()->setCursor(files.at(i));
break;
}
}
gamelist->setCursor(files.at(mid));
else {
if ((char)toupper(files.at(i)->getSortName()[0]) == letter) {
getGamelist()->setCursor(files.at(i));
break;
}
}
}
delete this;
}
void GuiGamelistOptions::findFirstFavorite()
void GuiGamelistOptions::jumpToFirstRow()
{
IGameListView* gamelist = getGamelist();
// this is a really shitty way to get a list of files
const std::vector<FileData*>& files = gamelist->getCursor()->getParent()->getChildrenListToDisplay();
long loop = 0;
long max = (long)files.size() - 1;
// Loop through the game list looking for the first game marked as a favorite
while (!files[loop]->getFavorite() and loop < max)
loop++;
// if the last entry in the game list was not a favorite then there were none for this system
if (!files[loop]->getFavorite())
{
firstFavorite = -1;
return;
}
firstFavorite = loop;
}
void GuiGamelistOptions::jumpToFirstFavorite()
{
IGameListView* gamelist = getGamelist();
// this is a really shitty way to get a list of files
const std::vector<FileData*>& files = gamelist->getCursor()->getParent()->getChildrenListToDisplay();
gamelist->setCursor(files.at(firstFavorite));
// Get first row of the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()->
getParent()->getChildrenListToDisplay();
getGamelist()->setCursor(files.at(0));
delete this;
}
bool GuiGamelistOptions::input(InputConfig* config, Input input)
{
if((config->isMappedTo("b", input) || config->isMappedTo("select", input)) && input.value)
{
if ((config->isMappedTo("b", input) ||
config->isMappedTo("select", input)) && input.value) {
delete this;
return true;
}

View file

@ -1,3 +1,10 @@
//
// GuiGamelistOptions.h
//
// Gamelist options menu for the 'Jump to...' quick selector,
// game sorting, game filters, and metadata edit.
//
#pragma once
#ifndef ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
#define ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H
@ -26,12 +33,11 @@ private:
void openMetaDataEd();
void startEditMode();
void exitEditMode();
void jumpToLetter();
void findFirstFavorite();
void jumpToFirstFavorite();
void jumpToFirstRow();
const std::string FAVORITE_CHAR = "\uF005";
long firstFavorite = -1;
MenuComponent mMenu;
@ -43,6 +49,7 @@ private:
SystemData* mSystem;
IGameListView* getGamelist();
bool mFavoritesSorting;
bool fromPlaceholder;
bool mFiltersChanged;
};

View file

@ -1,3 +1,9 @@
//
// GuiMenu.cpp
//
// Main menu.
//
#include "guis/GuiMenu.h"
#include "components/OptionListComponent.h"
@ -11,6 +17,7 @@
#include "guis/GuiSettings.h"
#include "views/UIModeController.h"
#include "views/ViewController.h"
#include "views/gamelist/IGameListView.h"
#include "CollectionSystemManager.h"
#include "EmulationStation.h"
#include "Scripting.h"
@ -20,7 +27,11 @@
#include <algorithm>
#include "platform.h"
GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MENU"), mVersion(window)
GuiMenu::GuiMenu(
Window* window)
: GuiComponent(window),
mMenu(window, "MAIN MENU"),
mVersion(window)
{
bool isFullUI = UIModeController::getInstance()->isUIModeFull();
@ -29,12 +40,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
addEntry("SOUND SETTINGS", 0x777777FF, true, [this] { openSoundSettings(); });
if (isFullUI)
addEntry("UI SETTINGS", 0x777777FF, true, [this] { openUISettings(); });
if (isFullUI)
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] { openCollectionSystemSettings(); });
addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, [this] {
openCollectionSystemSettings(); });
if (isFullUI)
addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { openOtherSettings(); });
@ -47,38 +58,44 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
addChild(&mMenu);
addVersionInfo();
setSize(mMenu.getSize());
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, Renderer::getScreenHeight() * 0.15f);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2,
Renderer::getScreenHeight() * 0.15f);
}
void GuiMenu::openScraperSettings()
{
auto s = new GuiSettings(mWindow, "SCRAPER");
// scrape from
auto scraper_list = std::make_shared< OptionListComponent< std::string > >(mWindow, "SCRAPE FROM", false);
// Scrape from.
auto scraper_list = std::make_shared< OptionListComponent< std::string >
>(mWindow, "SCRAPE FROM", false);
std::vector<std::string> scrapers = getScraperList();
// Select either the first entry of the one read from the settings, just in case the scraper from settings has vanished.
// Select either the first entry of the one read from the settings,
// just in case the scraper from settings has vanished.
for (auto it = scrapers.cbegin(); it != scrapers.cend(); it++)
scraper_list->add(*it, *it, *it == Settings::getInstance()->getString("Scraper"));
s->addWithLabel("SCRAPE FROM", scraper_list);
s->addSaveFunc([scraper_list] { Settings::getInstance()->setString("Scraper", scraper_list->getSelected()); });
s->addSaveFunc([scraper_list] { Settings::getInstance()->setString("Scraper",
scraper_list->getSelected()); });
// scrape ratings
// Scrape ratings.
auto scrape_ratings = std::make_shared<SwitchComponent>(mWindow);
scrape_ratings->setState(Settings::getInstance()->getBool("ScrapeRatings"));
s->addWithLabel("SCRAPE RATINGS", scrape_ratings);
s->addSaveFunc([scrape_ratings] { Settings::getInstance()->setBool("ScrapeRatings", scrape_ratings->getState()); });
s->addSaveFunc([scrape_ratings] { Settings::getInstance()->setBool("ScrapeRatings",
scrape_ratings->getState()); });
// scrape now
// Scrape now.
ComponentListRow row;
auto openScrapeNow = [this] { mWindow->pushGui(new GuiScraperStart(mWindow)); };
std::function<void()> openAndSave = openScrapeNow;
openAndSave = [s, openAndSave] { s->save(); openAndSave(); };
row.makeAcceptInputHandler(openAndSave);
auto scrape_now = std::make_shared<TextComponent>(mWindow, "SCRAPE NOW", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto scrape_now = std::make_shared<TextComponent>
(mWindow, "SCRAPE NOW", Font::get(FONT_SIZE_MEDIUM), 0x777777FF);
auto bracket = makeArrow(mWindow);
row.addElement(scrape_now, true);
row.addElement(bracket, false);
@ -91,17 +108,18 @@ void GuiMenu::openSoundSettings()
{
auto s = new GuiSettings(mWindow, "SOUND SETTINGS");
// volume
// System volume.
auto volume = std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%");
volume->setValue((float)VolumeControl::getInstance()->getVolume());
s->addWithLabel("SYSTEM VOLUME", volume);
s->addSaveFunc([volume] { VolumeControl::getInstance()->setVolume((int)Math::round(volume->getValue())); });
s->addSaveFunc([volume] { VolumeControl::getInstance()->
setVolume((int)Math::round(volume->getValue())); });
if (UIModeController::getInstance()->isUIModeFull())
{
if (UIModeController::getInstance()->isUIModeFull()) {
#if defined(__linux__)
// audio card
auto audio_card = std::make_shared< OptionListComponent<std::string> >(mWindow, "AUDIO CARD", false);
auto audio_card = std::make_shared< OptionListComponent<std::string>
>(mWindow, "AUDIO CARD", false);
std::vector<std::string> audio_cards;
#ifdef _RPI_
// RPi Specific Audio Cards
@ -116,7 +134,8 @@ void GuiMenu::openSoundSettings()
audio_cards.push_back("plughw");
audio_cards.push_back("null");
if (Settings::getInstance()->getString("AudioCard") != "") {
if(std::find(audio_cards.begin(), audio_cards.end(), Settings::getInstance()->getString("AudioCard")) == audio_cards.end()) {
if (std::find(audio_cards.begin(), audio_cards.end(),
Settings::getInstance()->getString("AudioCard")) == audio_cards.end()) {
audio_cards.push_back(Settings::getInstance()->getString("AudioCard"));
}
}
@ -129,8 +148,9 @@ void GuiMenu::openSoundSettings()
VolumeControl::getInstance()->init();
});
// volume control device
auto vol_dev = std::make_shared< OptionListComponent<std::string> >(mWindow, "AUDIO DEVICE", false);
// Volume control device.
auto vol_dev = std::make_shared< OptionListComponent<std::string>
>(mWindow, "AUDIO DEVICE", false);
std::vector<std::string> transitions;
transitions.push_back("PCM");
transitions.push_back("Speaker");
@ -138,7 +158,8 @@ void GuiMenu::openSoundSettings()
transitions.push_back("Digital");
transitions.push_back("Analogue");
if (Settings::getInstance()->getString("AudioDevice") != "") {
if(std::find(transitions.begin(), transitions.end(), Settings::getInstance()->getString("AudioDevice")) == transitions.end()) {
if (std::find(transitions.begin(), transitions.end(),
Settings::getInstance()->getString("AudioDevice")) == transitions.end()) {
transitions.push_back(Settings::getInstance()->getString("AudioDevice"));
}
}
@ -152,7 +173,7 @@ void GuiMenu::openSoundSettings()
});
#endif
// disable sounds
// Disable sounds.
auto sounds_enabled = std::make_shared<SwitchComponent>(mWindow);
sounds_enabled->setState(Settings::getInstance()->getBool("EnableSounds"));
s->addWithLabel("ENABLE NAVIGATION SOUNDS", sounds_enabled);
@ -170,11 +191,13 @@ void GuiMenu::openSoundSettings()
auto video_audio = std::make_shared<SwitchComponent>(mWindow);
video_audio->setState(Settings::getInstance()->getBool("VideoAudio"));
s->addWithLabel("ENABLE VIDEO AUDIO", video_audio);
s->addSaveFunc([video_audio] { Settings::getInstance()->setBool("VideoAudio", video_audio->getState()); });
s->addSaveFunc([video_audio] { Settings::getInstance()->setBool("VideoAudio",
video_audio->getState()); });
#ifdef _RPI_
// OMX player Audio Device
auto omx_audio_dev = std::make_shared< OptionListComponent<std::string> >(mWindow, "OMX PLAYER AUDIO DEVICE", false);
auto omx_audio_dev = std::make_shared< OptionListComponent<std::string>
>(mWindow, "OMX PLAYER AUDIO DEVICE", false);
std::vector<std::string> omx_cards;
// RPi Specific Audio Cards
omx_cards.push_back("local");
@ -183,7 +206,8 @@ void GuiMenu::openSoundSettings()
omx_cards.push_back("alsa:hw:0,0");
omx_cards.push_back("alsa:hw:1,0");
if (Settings::getInstance()->getString("OMXAudioDev") != "") {
if (std::find(omx_cards.begin(), omx_cards.end(), Settings::getInstance()->getString("OMXAudioDev")) == omx_cards.end()) {
if (std::find(omx_cards.begin(), omx_cards.end(),
Settings::getInstance()->getString("OMXAudioDev")) == omx_cards.end()) {
omx_cards.push_back(Settings::getInstance()->getString("OMXAudioDev"));
}
}
@ -198,26 +222,25 @@ void GuiMenu::openSoundSettings()
}
mWindow->pushGui(s);
}
void GuiMenu::openUISettings()
{
auto s = new GuiSettings(mWindow, "UI SETTINGS");
//UI mode
auto UImodeSelection = std::make_shared< OptionListComponent<std::string> >(mWindow, "UI MODE", false);
// UI mode.
auto UImodeSelection = std::make_shared< OptionListComponent<std::string>
>(mWindow, "UI MODE", false);
std::vector<std::string> UImodes = UIModeController::getInstance()->getUIModes();
for (auto it = UImodes.cbegin(); it != UImodes.cend(); it++)
UImodeSelection->add(*it, *it, Settings::getInstance()->getString("UIMode") == *it);
s->addWithLabel("UI MODE", UImodeSelection);
Window* window = mWindow;
s->addSaveFunc([ UImodeSelection, window]
{
s->addSaveFunc([ UImodeSelection, window] {
std::string selectedMode = UImodeSelection->getSelected();
if (selectedMode != "Full")
{
std::string msg = "You are changing the UI to a restricted mode:\n" + selectedMode + "\n";
if (selectedMode != "Full") {
std::string msg = "You are changing the UI to a restricted mode:\n" +
selectedMode + "\n";
msg += "This will hide most menu-options to prevent changes to the system.\n";
msg += "To unlock and return to the full UI, enter this code: \n";
msg += "\"" + UIModeController::getInstance()->getFormattedPassKeyStr() + "\"\n\n";
@ -231,8 +254,9 @@ void GuiMenu::openUISettings()
}
});
// fullscreen mode
auto fullscreen_mode = std::make_shared< OptionListComponent<std::string> >(mWindow, "FULLSCREEN MODE", false);
// Fullscreen mode.
auto fullscreen_mode = std::make_shared< OptionListComponent<std::string>
>(mWindow, "FULLSCREEN MODE", false);
std::vector<std::string> screenmode;
screenmode.push_back("normal");
screenmode.push_back("borderless");
@ -241,80 +265,82 @@ void GuiMenu::openUISettings()
s->addWithLabel("FULLSCREEN MODE (REQUIRES RESTART)", fullscreen_mode);
s->addSaveFunc([fullscreen_mode] {
if (Settings::getInstance()->getString("FullscreenMode") == "normal"
&& fullscreen_mode->getSelected() != "normal")
{
&& fullscreen_mode->getSelected() != "normal") {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
Settings::getInstance()->setString("FullscreenMode", fullscreen_mode->getSelected());
});
// screensaver
// Screensaver.
ComponentListRow screensaver_row;
screensaver_row.elements.clear();
screensaver_row.addElement(std::make_shared<TextComponent>(mWindow, "SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
screensaver_row.addElement(std::make_shared<TextComponent>
(mWindow, "SCREENSAVER SETTINGS", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
screensaver_row.addElement(makeArrow(mWindow), false);
screensaver_row.makeAcceptInputHandler(std::bind(&GuiMenu::openScreensaverOptions, this));
s->addRow(screensaver_row);
// quick system select (left/right in game list view)
// Quick system select (left/right in game list view).
auto quick_sys_select = std::make_shared<SwitchComponent>(mWindow);
quick_sys_select->setState(Settings::getInstance()->getBool("QuickSystemSelect"));
s->addWithLabel("QUICK SYSTEM SELECT", quick_sys_select);
s->addSaveFunc([quick_sys_select] { Settings::getInstance()->setBool("QuickSystemSelect", quick_sys_select->getState()); });
s->addSaveFunc([quick_sys_select] { Settings::getInstance()->setBool("QuickSystemSelect",
quick_sys_select->getState()); });
// carousel transition option
// Carousel transition option.
auto move_carousel = std::make_shared<SwitchComponent>(mWindow);
move_carousel->setState(Settings::getInstance()->getBool("MoveCarousel"));
s->addWithLabel("CAROUSEL TRANSITIONS", move_carousel);
s->addSaveFunc([move_carousel] {
if (move_carousel->getState()
&& !Settings::getInstance()->getBool("MoveCarousel")
&& PowerSaver::getMode() == PowerSaver::INSTANT)
{
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
Settings::getInstance()->setBool("MoveCarousel", move_carousel->getState());
});
// transition style
auto transition_style = std::make_shared< OptionListComponent<std::string> >(mWindow, "TRANSITION STYLE", false);
// Transition style.
auto transition_style = std::make_shared< OptionListComponent<std::string>
>(mWindow, "TRANSITION STYLE", false);
std::vector<std::string> transitions;
transitions.push_back("fade");
transitions.push_back("slide");
transitions.push_back("instant");
for (auto it = transitions.cbegin(); it != transitions.cend(); it++)
transition_style->add(*it, *it, Settings::getInstance()->getString("TransitionStyle") == *it);
transition_style->add(*it, *it, Settings::getInstance()->
getString("TransitionStyle") == *it);
s->addWithLabel("TRANSITION STYLE", transition_style);
s->addSaveFunc([transition_style] {
if (Settings::getInstance()->getString("TransitionStyle") == "instant"
&& transition_style->getSelected() != "instant"
&& PowerSaver::getMode() == PowerSaver::INSTANT)
{
&& PowerSaver::getMode() == PowerSaver::INSTANT) {
Settings::getInstance()->setString("PowerSaverMode", "default");
PowerSaver::init();
}
Settings::getInstance()->setString("TransitionStyle", transition_style->getSelected());
});
// theme set
// Theme selection.
auto themeSets = ThemeData::getThemeSets();
if (!themeSets.empty())
{
std::map<std::string, ThemeSet>::const_iterator selectedSet = themeSets.find(Settings::getInstance()->getString("ThemeSet"));
std::map<std::string, ThemeSet>::const_iterator selectedSet =
themeSets.find(Settings::getInstance()->getString("ThemeSet"));
if (selectedSet == themeSets.cend())
selectedSet = themeSets.cbegin();
auto theme_set = std::make_shared< OptionListComponent<std::string> >(mWindow, "THEME SET", false);
auto theme_set = std::make_shared< OptionListComponent<std::string>
>(mWindow, "THEME SET", false);
for (auto it = themeSets.cbegin(); it != themeSets.cend(); it++)
theme_set->add(it->first, it->first, it == selectedSet);
s->addWithLabel("THEME SET", theme_set);
Window* window = mWindow;
s->addSaveFunc([window, theme_set]
{
s->addSaveFunc([window, theme_set] {
bool needReload = false;
std::string oldTheme = Settings::getInstance()->getString("ThemeSet");
if (oldTheme != theme_set->getSelected())
@ -327,13 +353,15 @@ void GuiMenu::openUISettings()
Scripting::fireEvent("theme-changed", theme_set->getSelected(), oldTheme);
CollectionSystemManager::get()->updateSystemsList();
ViewController::get()->goToStart();
ViewController::get()->reloadAll(); // TODO - replace this with some sort of signal-based implementation
// TODO - replace this with some sort of signal-based implementation.
ViewController::get()->reloadAll();
}
});
}
// GameList view style
auto gamelist_style = std::make_shared< OptionListComponent<std::string> >(mWindow, "GAMELIST VIEW STYLE", false);
// GameList view style.
auto gamelist_style = std::make_shared< OptionListComponent<std::string>
>(mWindow, "GAMELIST VIEW STYLE", false);
std::vector<std::string> styles;
styles.push_back("automatic");
styles.push_back("basic");
@ -342,92 +370,121 @@ void GuiMenu::openUISettings()
styles.push_back("grid");
for (auto it = styles.cbegin(); it != styles.cend(); it++)
gamelist_style->add(*it, *it, Settings::getInstance()->getString("GamelistViewStyle") == *it);
gamelist_style->add(*it, *it, Settings::getInstance()->
getString("GamelistViewStyle") == *it);
s->addWithLabel("GAMELIST VIEW STYLE", gamelist_style);
s->addSaveFunc([gamelist_style] {
bool needReload = false;
if (Settings::getInstance()->getString("GamelistViewStyle") != gamelist_style->getSelected())
if (Settings::getInstance()->getString("GamelistViewStyle") !=
gamelist_style->getSelected())
needReload = true;
Settings::getInstance()->setString("GamelistViewStyle", gamelist_style->getSelected());
if (needReload)
ViewController::get()->reloadAll();
});
// Optionally start in selected system
auto systemfocus_list = std::make_shared< OptionListComponent<std::string> >(mWindow, "START ON SYSTEM", false);
// Optionally start in selected system.
auto systemfocus_list = std::make_shared< OptionListComponent<std::string>
>(mWindow, "START ON SYSTEM", false);
systemfocus_list->add("NONE", "", Settings::getInstance()->getString("StartupSystem") == "");
for (auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
{
for (auto it = SystemData::sSystemVector.cbegin();
it != SystemData::sSystemVector.cend(); it++) {
if ("retropie" != (*it)->getName())
{
systemfocus_list->add((*it)->getName(), (*it)->getName(), Settings::getInstance()->getString("StartupSystem") == (*it)->getName());
}
systemfocus_list->add((*it)->getName(), (*it)->getName(),
Settings::getInstance()->getString("StartupSystem") == (*it)->getName());
}
s->addWithLabel("START ON SYSTEM", systemfocus_list);
s->addSaveFunc([systemfocus_list] {
Settings::getInstance()->setString("StartupSystem", systemfocus_list->getSelected());
});
// show help
// Show help.
auto show_help = std::make_shared<SwitchComponent>(mWindow);
show_help->setState(Settings::getInstance()->getBool("ShowHelpPrompts"));
s->addWithLabel("ON-SCREEN HELP", show_help);
s->addSaveFunc([show_help] { Settings::getInstance()->setBool("ShowHelpPrompts", show_help->getState()); });
s->addSaveFunc([show_help] { Settings::getInstance()->setBool("ShowHelpPrompts",
show_help->getState()); });
// enable filters (ForceDisableFilters)
// Enable filters (ForceDisableFilters).
auto enable_filter = std::make_shared<SwitchComponent>(mWindow);
enable_filter->setState(!Settings::getInstance()->getBool("ForceDisableFilters"));
s->addWithLabel("ENABLE FILTERS", enable_filter);
s->addWithLabel("ENABLE GAMELIST FILTERS", enable_filter);
s->addSaveFunc([enable_filter] {
bool filter_is_enabled = !Settings::getInstance()->getBool("ForceDisableFilters");
Settings::getInstance()->setBool("ForceDisableFilters", !enable_filter->getState());
if (enable_filter->getState() != filter_is_enabled) ViewController::get()->ReloadAndGoToStart();
if (enable_filter->getState() != filter_is_enabled)
ViewController::get()->ReloadAndGoToStart();
});
// hide start menu in Kid Mode
// Hide start menu in Kid Mode.
auto disable_start = std::make_shared<SwitchComponent>(mWindow);
disable_start->setState(Settings::getInstance()->getBool("DisableKidStartMenu"));
s->addWithLabel("DISABLE START MENU IN KID MODE", disable_start);
s->addSaveFunc([disable_start] { Settings::getInstance()->setBool("DisableKidStartMenu", disable_start->getState()); });
s->addSaveFunc([disable_start] { Settings::getInstance()->setBool("DisableKidStartMenu",
disable_start->getState()); });
// hide Reboot System option in the Quit menu
// Hide Reboot System option in the quit menu.
auto show_rebootsystem = std::make_shared<SwitchComponent>(mWindow);
show_rebootsystem->setState(Settings::getInstance()->getBool("ShowRebootSystem"));
s->addWithLabel("SHOW \"REBOOT SYSTEM\" MENU ENTRY", show_rebootsystem);
s->addSaveFunc([show_rebootsystem] { Settings::getInstance()->setBool("ShowRebootSystem", show_rebootsystem->getState()); });
s->addSaveFunc([show_rebootsystem] { Settings::getInstance()->setBool("ShowRebootSystem",
show_rebootsystem->getState()); });
// hide Power Off System option in the Quit menu
// Hide Power Off System option in the quit menu.
auto show_poweroffsystem = std::make_shared<SwitchComponent>(mWindow);
show_poweroffsystem->setState(Settings::getInstance()->getBool("ShowPoweroffSystem"));
s->addWithLabel("SHOW \"POWER OFF SYSTEM\" MENU ENTRY", show_poweroffsystem);
s->addSaveFunc([show_poweroffsystem] { Settings::getInstance()->setBool("ShowPoweroffSystem", show_poweroffsystem->getState()); });
s->addSaveFunc([show_poweroffsystem] { Settings::getInstance()->setBool("ShowPoweroffSystem",
show_poweroffsystem->getState()); });
// Show favorites first in gamelists
// Sort favorites on top of the gamelists.
auto favoritesFirstSwitch = std::make_shared<SwitchComponent>(mWindow);
favoritesFirstSwitch->setState(Settings::getInstance()->getBool("FavoritesFirst"));
s->addWithLabel("SHOW FAVORITES ON TOP OF GAMELIST", favoritesFirstSwitch);
s->addSaveFunc([favoritesFirstSwitch]
{
s->addWithLabel("SORT FAVORITES ON TOP OF GAMELISTS", favoritesFirstSwitch);
s->addSaveFunc([favoritesFirstSwitch] {
if (Settings::getInstance()->setBool("FavoritesFirst", favoritesFirstSwitch->getState()))
ViewController::get()->reloadAll();
for (auto it = SystemData::sSystemVector.cbegin(); it !=
SystemData::sSystemVector.cend(); it++) {
// The favorites and recent gamelists never sort favorites on top.
if ((*it)->getName() == "favorites" || (*it)->getName() == "recent")
continue;
// Don't re-sort custom collections as they have their own option
// for whether to sort favorites on top or not (FavFirstCustom).
if (CollectionSystemManager::get()->getIsCustomCollection((*it)))
continue;
FileData* rootFolder = (*it)->getRootFolder();
rootFolder->sort(getSortTypeFromString(rootFolder->getSortTypeString()),
Settings::getInstance()->getBool("FavoritesFirst"));
ViewController::get()->reloadGameListView(*it);
// Jump to the first row of the game list.
IGameListView* gameList = ViewController::get()->getGameListView((*it)).get();
FileData* firstRow = gameList->getCursor()->getParent()->getChildrenListToDisplay()[0];
gameList->setCursor(firstRow);
}
});
mWindow->pushGui(s);
}
void GuiMenu::openOtherSettings()
{
auto s = new GuiSettings(mWindow, "OTHER SETTINGS");
// maximum vram
// Maximum VRAM.
auto max_vram = std::make_shared<SliderComponent>(mWindow, 0.f, 1000.f, 10.f, "Mb");
max_vram->setValue((float)(Settings::getInstance()->getInt("MaxVRAM")));
s->addWithLabel("VRAM LIMIT", max_vram);
s->addSaveFunc([max_vram] { Settings::getInstance()->setInt("MaxVRAM", (int)Math::round(max_vram->getValue())); });
s->addSaveFunc([max_vram] { Settings::getInstance()->setInt("MaxVRAM",
(int)Math::round(max_vram->getValue())); });
// power saver
auto power_saver = std::make_shared< OptionListComponent<std::string> >(mWindow, "POWER SAVER MODES", false);
// Power saver.
auto power_saver = std::make_shared< OptionListComponent<std::string>
>(mWindow, "POWER SAVER MODES", false);
std::vector<std::string> modes;
modes.push_back("disabled");
modes.push_back("default");
@ -437,7 +494,8 @@ void GuiMenu::openOtherSettings()
power_saver->add(*it, *it, Settings::getInstance()->getString("PowerSaverMode") == *it);
s->addWithLabel("POWER SAVER MODES", power_saver);
s->addSaveFunc([this, power_saver] {
if (Settings::getInstance()->getString("PowerSaverMode") != "instant" && power_saver->getSelected() == "instant") {
if (Settings::getInstance()->getString("PowerSaverMode") !=
"instant" && power_saver->getSelected() == "instant") {
Settings::getInstance()->setString("TransitionStyle", "instant");
Settings::getInstance()->setBool("MoveCarousel", false);
Settings::getInstance()->setBool("EnableSounds", false);
@ -446,15 +504,17 @@ void GuiMenu::openOtherSettings()
PowerSaver::init();
});
// gamelists
auto gamelistsSaveMode = std::make_shared< OptionListComponent<std::string> >(mWindow, "SAVE METADATA", false);
// Gamelists.
auto gamelistsSaveMode = std::make_shared< OptionListComponent<std::string>
>(mWindow, "SAVE METADATA", false);
std::vector<std::string> saveModes;
saveModes.push_back("on exit");
saveModes.push_back("always");
saveModes.push_back("never");
for (auto it = saveModes.cbegin(); it != saveModes.cend(); it++)
gamelistsSaveMode->add(*it, *it, Settings::getInstance()->getString("SaveGamelistsMode") == *it);
gamelistsSaveMode->add(*it, *it, Settings::getInstance()->
getString("SaveGamelistsMode") == *it);
s->addWithLabel("SAVE METADATA", gamelistsSaveMode);
s->addSaveFunc([gamelistsSaveMode] {
Settings::getInstance()->setString("SaveGamelistsMode", gamelistsSaveMode->getSelected());
@ -463,33 +523,38 @@ void GuiMenu::openOtherSettings()
auto parse_gamelists = std::make_shared<SwitchComponent>(mWindow);
parse_gamelists->setState(Settings::getInstance()->getBool("ParseGamelistOnly"));
s->addWithLabel("PARSE GAMESLISTS ONLY", parse_gamelists);
s->addSaveFunc([parse_gamelists] { Settings::getInstance()->setBool("ParseGamelistOnly", parse_gamelists->getState()); });
s->addSaveFunc([parse_gamelists] { Settings::getInstance()->
setBool("ParseGamelistOnly", parse_gamelists->getState()); });
auto local_art = std::make_shared<SwitchComponent>(mWindow);
local_art->setState(Settings::getInstance()->getBool("LocalArt"));
s->addWithLabel("SEARCH FOR LOCAL ART", local_art);
s->addSaveFunc([local_art] { Settings::getInstance()->setBool("LocalArt", local_art->getState()); });
s->addSaveFunc([local_art] { Settings::getInstance()->
setBool("LocalArt", local_art->getState()); });
// Allow overriding of the launch string per game (the option to disable this is intended primarily for testing purposes)
// Allow overriding of the launch string per game (the option
// to disable this is intended primarily for testing purposes).
auto launchstring_override = std::make_shared<SwitchComponent>(mWindow);
launchstring_override->setState(Settings::getInstance()->getBool("LaunchstringOverride"));
s->addWithLabel("PER GAME OVERRIDE OF LAUNCH STRING", launchstring_override);
s->addSaveFunc([launchstring_override] { Settings::getInstance()->setBool("LaunchstringOverride", launchstring_override->getState()); });
s->addWithLabel("PER GAME LAUNCH STRING OVERRIDE", launchstring_override);
s->addSaveFunc([launchstring_override] { Settings::getInstance()->
setBool("LaunchstringOverride", launchstring_override->getState()); });
// hidden files
// Hidden files.
auto hidden_files = std::make_shared<SwitchComponent>(mWindow);
hidden_files->setState(Settings::getInstance()->getBool("ShowHiddenFiles"));
s->addWithLabel("SHOW HIDDEN FILES", hidden_files);
s->addSaveFunc([hidden_files] { Settings::getInstance()->setBool("ShowHiddenFiles", hidden_files->getState()); });
s->addSaveFunc([hidden_files] { Settings::getInstance()->setBool("ShowHiddenFiles",
hidden_files->getState()); });
#ifdef _RPI_
// Video Player - VideoOmxPlayer
// Video Player - VideoOmxPlayer.
auto omx_player = std::make_shared<SwitchComponent>(mWindow);
omx_player->setState(Settings::getInstance()->getBool("VideoOmxPlayer"));
s->addWithLabel("USE OMX PLAYER (HW ACCELERATED)", omx_player);
s->addSaveFunc([omx_player]
{
// need to reload all views to re-create the right video components
// Need to reload all views to re-create the right video components.
bool needReload = false;
if (Settings::getInstance()->getBool("VideoOmxPlayer") != omx_player->getState())
needReload = true;
@ -502,12 +567,12 @@ void GuiMenu::openOtherSettings()
#endif
// framerate
// Framerate.
auto framerate = std::make_shared<SwitchComponent>(mWindow);
framerate->setState(Settings::getInstance()->getBool("DrawFramerate"));
s->addWithLabel("SHOW FRAMERATE", framerate);
s->addSaveFunc([framerate] { Settings::getInstance()->setBool("DrawFramerate", framerate->getState()); });
s->addSaveFunc([framerate] { Settings::getInstance()->setBool("DrawFramerate",
framerate->getState()); });
mWindow->pushGui(s);
@ -521,7 +586,6 @@ void GuiMenu::openConfigInput()
window->pushGui(new GuiDetectDevice(window, false, nullptr));
}, "NO", nullptr)
);
}
void GuiMenu::openQuitMenu()
@ -531,10 +595,8 @@ void GuiMenu::openQuitMenu()
Window* window = mWindow;
ComponentListRow row;
if (UIModeController::getInstance()->isUIModeFull())
{
if(Settings::getInstance()->getBool("ShowExit"))
{
if (UIModeController::getInstance()->isUIModeFull()) {
if (Settings::getInstance()->getBool("ShowExit")) {
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY QUIT?", "YES",
[] {
@ -542,13 +604,13 @@ void GuiMenu::openQuitMenu()
quitES();
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "QUIT EMULATIONSTATION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(window, "QUIT EMULATIONSTATION",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
s->addRow(row);
}
}
if(Settings::getInstance()->getBool("ShowRebootSystem"))
{
if (Settings::getInstance()->getBool("ShowRebootSystem")) {
row.elements.clear();
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY REBOOT?", "YES",
@ -559,12 +621,12 @@ void GuiMenu::openQuitMenu()
LOG(LogWarning) << "Reboot terminated with non-zero result!";
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "REBOOT SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(window, "REBOOT SYSTEM",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
s->addRow(row);
}
if(Settings::getInstance()->getBool("ShowPoweroffSystem"))
{
if (Settings::getInstance()->getBool("ShowPoweroffSystem")) {
row.elements.clear();
row.makeAcceptInputHandler([window] {
window->pushGui(new GuiMsgBox(window, "REALLY POWER OFF?", "YES",
@ -575,7 +637,8 @@ void GuiMenu::openQuitMenu()
LOG(LogWarning) << "Power off terminated with non-zero result!";
}, "NO", nullptr));
});
row.addElement(std::make_shared<TextComponent>(window, "POWER OFF SYSTEM", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(std::make_shared<TextComponent>(window, "POWER OFF SYSTEM",
Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
s->addRow(row);
}
@ -584,8 +647,6 @@ void GuiMenu::openQuitMenu()
void GuiMenu::addVersionInfo()
{
// std::string buildDate = (Settings::getInstance()->getBool("Debug") ? std::string( " (" + Utils::String::toUpper(PROGRAM_BUILT_STRING) + ")") : (""));
mVersion.setFont(Font::get(FONT_SIZE_SMALL));
mVersion.setColor(0x5E5E5EFF);
mVersion.setText("EMULATIONSTATION-DE V" + Utils::String::toUpper(PROGRAM_VERSION_STRING));
@ -607,16 +668,19 @@ void GuiMenu::onSizeChanged()
mVersion.setPosition(0, mSize.y() - mVersion.getSize().y());
}
void GuiMenu::addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func)
void GuiMenu::addEntry(
const char* name,
unsigned int color,
bool add_arrow,
const std::function<void()>& func)
{
std::shared_ptr<Font> font = Font::get(FONT_SIZE_MEDIUM);
// populate the list
// Populate the list.
ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, name, font, color), true);
if(add_arrow)
{
if (add_arrow) {
std::shared_ptr<ImageComponent> bracket = makeArrow(mWindow);
row.addElement(bracket, false);
}
@ -631,8 +695,8 @@ bool GuiMenu::input(InputConfig* config, Input input)
if (GuiComponent::input(config, input))
return true;
if((config->isMappedTo("b", input) || config->isMappedTo("start", input)) && input.value != 0)
{
if ((config->isMappedTo("b", input) || config->isMappedTo("start", input)) &&
input.value != 0) {
delete this;
return true;
}

View file

@ -1,3 +1,9 @@
//
// GuiMenu.h
//
// Main menu.
//
#pragma once
#ifndef ES_APP_GUIS_GUI_MENU_H
#define ES_APP_GUIS_GUI_MENU_H
@ -16,7 +22,8 @@ public:
HelpStyle getHelpStyle() override;
private:
void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function<void()>& func);
void addEntry(const char* name, unsigned int color,
bool add_arrow, const std::function<void()>& func);
void addVersionInfo();
void openCollectionSystemSettings();
void openConfigInput();

View file

@ -1,3 +1,12 @@
//
// ViewController.cpp
//
// Handles overall system navigation including animations and transitions.
// Also creates the gamelist views and handles refresh and reloads of these when needed
// (for example when metadata has been changed or when a list sorting has taken place).
// Initiates the launching of games, calling FileData to do the actual launch.
//
#include "views/ViewController.h"
#include "animations/Animation.h"
@ -18,7 +27,7 @@
#include "Window.h"
#include "Sound.h"
ViewController* ViewController::sInstance = NULL;
ViewController* ViewController::sInstance = nullptr;
NavigationSounds navigationsounds;
ViewController* ViewController::get()
@ -33,8 +42,13 @@ void ViewController::init(Window* window)
sInstance = new ViewController(window);
}
ViewController::ViewController(Window* window)
: GuiComponent(window), mCurrentView(nullptr), mCamera(Transform4x4f::Identity()), mFadeOpacity(0), mLockInput(false)
ViewController::ViewController(
Window* window)
: GuiComponent(window),
mCurrentView(nullptr),
mCamera(Transform4x4f::Identity()),
mFadeOpacity(0),
mLockInput(false)
{
mState.viewing = NOTHING;
}
@ -42,24 +56,23 @@ ViewController::ViewController(Window* window)
ViewController::~ViewController()
{
assert(sInstance == this);
sInstance = NULL;
sInstance = nullptr;
}
void ViewController::goToStart()
{
// If specific system is requested, go directly to the game list
// If a specific system is requested, go directly to its game list.
auto requestedSystem = Settings::getInstance()->getString("StartupSystem");
if("" != requestedSystem && "retropie" != requestedSystem)
{
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++){
if ((*it)->getName() == requestedSystem)
{
if ("" != requestedSystem && "retropie" != requestedSystem) {
for (auto it = SystemData::sSystemVector.cbegin();
it != SystemData::sSystemVector.cend(); it++) {
if ((*it)->getName() == requestedSystem) {
goToGameList(*it);
return;
}
}
// Requested system doesn't exist
// Requested system doesn't exist.
Settings::getInstance()->setString("StartupSystem", "");
}
goToSystemView(SystemData::sSystemVector.at(0));
@ -80,17 +93,16 @@ int ViewController::getSystemId(SystemData* system)
void ViewController::goToSystemView(SystemData* system)
{
// Tell any current view it's about to be hidden
// Tell any current view it's about to be hidden.
if (mCurrentView)
{
mCurrentView->onHide();
}
mState.viewing = SYSTEM_SELECT;
mState.system = system;
auto systemList = getSystemListView();
systemList->setPosition(getSystemId(system) * (float)Renderer::getScreenWidth(), systemList->getPosition().y());
systemList->setPosition(getSystemId(system) * (float)Renderer::getScreenWidth(),
systemList->getPosition().y());
systemList->goToSystem(system, false);
mCurrentView = systemList;
@ -120,13 +132,14 @@ void ViewController::goToPrevGameList()
void ViewController::goToGameList(SystemData* system)
{
if(mState.viewing == SYSTEM_SELECT)
{
// move system list
if (mState.viewing == SYSTEM_SELECT) {
// Move system list.
auto sysList = getSystemListView();
float offX = sysList->getPosition().x();
int sysId = getSystemId(system);
sysList->setPosition(sysId * (float)Renderer::getScreenWidth(), sysList->getPosition().y());
sysList->setPosition(sysId * (float)Renderer::getScreenWidth(),
sysList->getPosition().y());
offX = sysList->getPosition().x() - offX;
mCamera.translation().x() -= offX;
}
@ -135,14 +148,12 @@ void ViewController::goToGameList(SystemData* system)
mState.system = system;
if (mCurrentView)
{
mCurrentView->onHide();
}
mCurrentView = getGameListView(system);
if (mCurrentView)
{
mCurrentView->onShow();
}
playViewTransition();
}
@ -152,50 +163,50 @@ void ViewController::playViewTransition()
if (mCurrentView)
target = mCurrentView->getPosition();
// no need to animate, we're not going anywhere (probably goToNextGamelist() or goToPrevGamelist() when there's only 1 system)
// No need to animate, we're not going anywhere (probably due to goToNextGamelist()
// or goToPrevGamelist() being called when there's only 1 system).
if (target == -mCamera.translation() && !isAnimationPlaying(0))
return;
std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
if(transition_style == "fade")
{
// fade
// stop whatever's currently playing, leaving mFadeOpacity wherever it is
if (transition_style == "fade") {
// Fade.
// Stop whatever's currently playing, leaving mFadeOpacity wherever it is.
cancelAnimation(0);
auto fadeFunc = [this](float t) {
mFadeOpacity = Math::lerp(0, 1, t);
};
const static int FADE_DURATION = 240; // fade in/out time
const static int FADE_WAIT = 320; // time to wait between in/out
const static int FADE_DURATION = 240; // Fade in/out time.
const static int FADE_WAIT = 320; // Time to wait between in/out.
setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), 0, [this, fadeFunc, target] {
this->mCamera.translation() = -target;
updateHelpPrompts();
setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), FADE_WAIT, nullptr, true);
});
// fast-forward animation if we're partway faded
if(target == -mCamera.translation())
{
// not changing screens, so cancel the first half entirely
// Fast-forward animation if we're partway faded.
if (target == -mCamera.translation()) {
// Not changing screens, so cancel the first half entirely.
advanceAnimation(0, FADE_DURATION);
advanceAnimation(0, FADE_WAIT);
advanceAnimation(0, FADE_DURATION - (int)(mFadeOpacity * FADE_DURATION));
}else{
}
else {
advanceAnimation(0, (int)(mFadeOpacity * FADE_DURATION));
}
} else if (transition_style == "slide"){
// slide or simple slide
}
else if (transition_style == "slide") {
// Slide or simple slide.
setAnimation(new MoveCameraAnimation(mCamera, target));
updateHelpPrompts(); // update help prompts immediately
} else {
// instant
setAnimation(new LambdaAnimation(
[this, target](float /*t*/)
{
this->mCamera.translation() = -target;
}, 1));
updateHelpPrompts(); // Update help prompts immediately.
}
else {
// Instant.
setAnimation(new LambdaAnimation([this, target](float /*t*/) {
this->mCamera.translation() = -target; }, 1));
updateHelpPrompts();
}
}
@ -209,13 +220,12 @@ void ViewController::onFileChanged(FileData* file, FileChangeType change)
void ViewController::launch(FileData* game, Vector3f center)
{
if(game->getType() != GAME)
{
if (game->getType() != GAME) {
LOG(LogError) << "tried to launch something that isn't a game";
return;
}
// Hide the current view
// Hide the current view.
if (mCurrentView)
mCurrentView->onHide();
@ -223,47 +233,51 @@ void ViewController::launch(FileData* game, Vector3f center)
origCamera.translation() = -mCurrentView->getPosition();
center += mCurrentView->getPosition();
stopAnimation(1); // make sure the fade in isn't still playing
mWindow->stopInfoPopup(); // make sure we disable any existing info popup
stopAnimation(1); // Make sure the fade in isn't still playing.
mWindow->stopInfoPopup(); // Make sure we disable any existing info popup.
mLockInput = true;
std::string transition_style = Settings::getInstance()->getString("TransitionStyle");
navigationsounds.playThemeNavigationSound(LAUNCHSOUND);
// let launch sound play to the end before launching game
// Let launch sound play to the end before launching game.
while(navigationsounds.isPlayingThemeNavigationSound(LAUNCHSOUND));
if(transition_style == "fade")
{
// fade out, launch game, fade back in
if (transition_style == "fade") {
// Fade out, launch game, fade back in.
auto fadeFunc = [this](float t) {
mFadeOpacity = Math::lerp(0.0f, 1.0f, t);
};
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc]
{
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc] {
game->launchGame(mWindow);
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this] { mLockInput = false; }, true);
setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this] {
mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
mCurrentView->onShow();
});
} else if (transition_style == "slide"){
// move camera to zoom in on center + fade out, launch game, come back in
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), 0, [this, origCamera, center, game]
{
}
else if (transition_style == "slide") {
// Move camera to zoom in on center + fade out, launch game, come back in.
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), 0,
[this, origCamera, center, game] {
game->launchGame(mWindow);
mCamera = origCamera;
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this] { mLockInput = false; }, true);
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, [this] {
mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
mCurrentView->onShow();
});
} else { // instant
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, origCamera, center, game]
{
}
// Instant
else {
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0,
[this, origCamera, center, game] {
game->launchGame(mWindow);
mCamera = origCamera;
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this] { mLockInput = false; }, true);
setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0,
[this] { mLockInput = false; }, true);
this->onFileChanged(game, FILE_METADATA_CHANGED);
if (mCurrentView)
mCurrentView->onShow();
@ -273,10 +287,8 @@ void ViewController::launch(FileData* game, Vector3f center)
void ViewController::removeGameListView(SystemData* system)
{
//if we already made one, return that one
auto exists = mGameListViews.find(system);
if(exists != mGameListViews.cend())
{
if (exists != mGameListViews.cend()) {
exists->second.reset();
mGameListViews.erase(system);
}
@ -284,18 +296,18 @@ void ViewController::removeGameListView(SystemData* system)
std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* system)
{
//if we already made one, return that one
// If we already made one, return that one.
auto exists = mGameListViews.find(system);
if (exists != mGameListViews.cend())
return exists->second;
system->getIndex()->setUIModeFilters();
//if we didn't, make it, remember it, and return it
// If we didn't, make it, remember it, and return it.
std::shared_ptr<IGameListView> view;
bool themeHasVideoView = system->getTheme()->hasView("video");
//decide type
// Decide type.
GameListViewType selectedViewType = AUTOMATIC;
std::string viewPreference = Settings::getInstance()->getString("GamelistViewStyle");
@ -308,39 +320,39 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
if (viewPreference.compare("video") == 0)
selectedViewType = VIDEO;
if (selectedViewType == AUTOMATIC)
{
if (selectedViewType == AUTOMATIC) {
std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
for (auto it = files.cbegin(); it != files.cend(); it++)
{
if (themeHasVideoView && !(*it)->getVideoPath().empty())
{
for (auto it = files.cbegin(); it != files.cend(); it++) {
if (themeHasVideoView && !(*it)->getVideoPath().empty()) {
selectedViewType = VIDEO;
break;
}
else if (!(*it)->getThumbnailPath().empty())
{
else if (!(*it)->getThumbnailPath().empty()) {
selectedViewType = DETAILED;
// Don't break out in case any subsequent files have video
// Don't break out in case any subsequent files have videos.
}
}
}
// Create the view
// Create the view.
switch (selectedViewType)
{
case VIDEO:
view = std::shared_ptr<IGameListView>(new VideoGameListView(mWindow, system->getRootFolder()));
view = std::shared_ptr<IGameListView>(
new VideoGameListView(mWindow, system->getRootFolder()));
break;
case DETAILED:
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
view = std::shared_ptr<IGameListView>(
new DetailedGameListView(mWindow, system->getRootFolder()));
break;
case GRID:
view = std::shared_ptr<IGameListView>(new GridGameListView(mWindow, system->getRootFolder()));
view = std::shared_ptr<IGameListView>(
new GridGameListView(mWindow, system->getRootFolder()));
break;
case BASIC:
default:
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
view = std::shared_ptr<IGameListView>(
new BasicGameListView(mWindow, system->getRootFolder()));
break;
}
@ -348,7 +360,8 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
int id = (int)(std::find(sysVec.cbegin(), sysVec.cend(), system) - sysVec.cbegin());
view->setPosition(id * (float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight() * 2);
view->setPosition(id * (float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight() * 2);
addChild(view.get());
@ -358,7 +371,7 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
std::shared_ptr<SystemView> ViewController::getSystemListView()
{
//if we already made one, return that one
// If we already made one, return that one.
if (mSystemListView)
return mSystemListView;
@ -374,18 +387,17 @@ bool ViewController::input(InputConfig* config, Input input)
if (mLockInput)
return true;
// open menu
if(!(UIModeController::getInstance()->isUIModeKid() && Settings::getInstance()->getBool("DisableKidStartMenu")) && config->isMappedTo("start", input) && input.value != 0)
{
// open menu
// Open menu.
if (!(UIModeController::getInstance()->isUIModeKid() &&
Settings::getInstance()->getBool("DisableKidStartMenu")) &&
config->isMappedTo("start", input) && input.value != 0) {
mWindow->pushGui(new GuiMenu(mWindow));
return true;
}
if(UIModeController::getInstance()->listen(config, input)) // check if UI mode has changed due to passphrase completion
{
// Check if UI mode has changed due to passphrase completion.
if (UIModeController::getInstance()->listen(config, input))
return true;
}
if (mCurrentView)
return mCurrentView->input(config, input);
@ -396,9 +408,7 @@ bool ViewController::input(InputConfig* config, Input input)
void ViewController::update(int deltaTime)
{
if (mCurrentView)
{
mCurrentView->update(deltaTime);
}
updateSelf(deltaTime);
}
@ -409,22 +419,23 @@ void ViewController::render(const Transform4x4f& parentTrans)
Transform4x4f transInverse;
transInverse.invert(trans);
// camera position, position + size
// Camera position, position + size.
Vector3f viewStart = transInverse.translation();
Vector3f viewEnd = transInverse * Vector3f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight(), 0);
Vector3f viewEnd = transInverse * Vector3f((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight(), 0);
// Keep track of UI mode changes.
UIModeController::getInstance()->monitorUIMode();
// draw systemview
// Draw system view.
getSystemListView()->render(trans);
// draw gamelists
for(auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++)
{
// clipping
// Draw gamelists.
for (auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++) {
// Clipping.
Vector3f guiStart = it->second->getPosition();
Vector3f guiEnd = it->second->getPosition() + Vector3f(it->second->getSize().x(), it->second->getSize().y(), 0);
Vector3f guiEnd = it->second->getPosition() + Vector3f(it->second->getSize().x(),
it->second->getSize().y(), 0);
if (guiEnd.x() >= viewStart.x() && guiEnd.y() >= viewStart.y() &&
guiStart.x() <= viewEnd.x() && guiStart.y() <= viewEnd.y())
@ -434,23 +445,22 @@ void ViewController::render(const Transform4x4f& parentTrans)
if (mWindow->peekGui() == this)
mWindow->renderHelpPromptsEarly();
// fade out
if(mFadeOpacity)
{
// Fade out.
if (mFadeOpacity) {
unsigned int fadeColor = 0x00000000 | (unsigned char)(mFadeOpacity * 255);
Renderer::setMatrix(parentTrans);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), fadeColor, fadeColor);
Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(),
Renderer::getScreenHeight(), fadeColor, fadeColor);
}
}
void ViewController::preload()
{
uint32_t i = 0;
for(auto it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); it++)
{
for (auto it = SystemData::sSystemVector.cbegin();
it != SystemData::sSystemVector.cend(); it++) {
if (Settings::getInstance()->getBool("SplashScreen") &&
Settings::getInstance()->getBool("SplashScreenProgress"))
{
Settings::getInstance()->getBool("SplashScreenProgress")) {
i++;
char buffer[100];
sprintf (buffer, "Loading '%s' (%d/%d)",
@ -461,16 +471,14 @@ void ViewController::preload()
(*it)->getIndex()->resetFilters();
getGameListView(*it);
}
// load navigation sounds
// Load navigation sounds.
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
}
void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
{
for(auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++)
{
if(it->second.get() == view)
{
for (auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++) {
if (it->second.get() == view) {
bool isCurrent = (mCurrentView == it->second);
SystemData* system = it->first;
FileData* cursor = view->getCursor();
@ -481,7 +489,7 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
system->getIndex()->setUIModeFilters();
std::shared_ptr<IGameListView> newView = getGameListView(system);
// to counter having come from a placeholder
// To counter having come from a placeholder.
if (!cursor->isPlaceHolder()) {
newView->setCursor(cursor);
}
@ -491,50 +499,46 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
break;
}
}
// Redisplay the current view
// Redisplay the current view.
if (mCurrentView)
mCurrentView->onShow();
}
void ViewController::reloadAll()
{
// clear all gamelistviews
// Clear all GameListViews.
std::map<SystemData*, FileData*> cursorMap;
for (auto it = mGameListViews.cbegin(); it != mGameListViews.cend(); it++)
{
cursorMap[it->first] = it->second->getCursor();
}
mGameListViews.clear();
// load themes, create gamelistviews and reset filters
for(auto it = cursorMap.cbegin(); it != cursorMap.cend(); it++)
{
// Load themes, create GameListViews and reset filters.
for (auto it = cursorMap.cbegin(); it != cursorMap.cend(); it++) {
it->first->loadTheme();
it->first->getIndex()->resetFilters();
getGameListView(it->first)->setCursor(it->second);
}
// Rebuild SystemListView
// Rebuild SystemListView.
mSystemListView.reset();
getSystemListView();
// update mCurrentView since the pointers changed
if(mState.viewing == GAME_LIST)
{
// Update mCurrentView since the pointers changed.
if (mState.viewing == GAME_LIST) {
mCurrentView = getGameListView(mState.getSystem());
}else if(mState.viewing == SYSTEM_SELECT)
{
}
else if (mState.viewing == SYSTEM_SELECT) {
SystemData* system = mState.getSystem();
goToSystemView(SystemData::sSystemVector.front());
mSystemListView->goToSystem(system, false);
mCurrentView = mSystemListView;
}else{
}
else {
goToSystemView(SystemData::sSystemVector.front());
}
// load navigation sounds
// Load navigation sounds.
navigationsounds.loadThemeNavigationSounds(SystemData::sSystemVector[0]->getTheme());
updateHelpPrompts();
@ -547,9 +551,9 @@ std::vector<HelpPrompt> ViewController::getHelpPrompts()
return prompts;
prompts = mCurrentView->getHelpPrompts();
if(!(UIModeController::getInstance()->isUIModeKid() && Settings::getInstance()->getBool("DisableKidStartMenu")))
if (!(UIModeController::getInstance()->isUIModeKid() &&
Settings::getInstance()->getBool("DisableKidStartMenu")))
prompts.push_back(HelpPrompt("start", "menu"));
return prompts;
}

View file

@ -1,3 +1,12 @@
//
// ViewController.h
//
// Handles overall system navigation including animations and transitions.
// Also creates the gamelist views and handles refresh and reloads of these when needed
// (for example when metadata has been changed or when a list sorting has taken place).
// Initiates the launching of games, calling FileData to do the actual launch.
//
#pragma once
#ifndef ES_APP_VIEWS_VIEW_CONTROLLER_H
#define ES_APP_VIEWS_VIEW_CONTROLLER_H
@ -11,7 +20,8 @@ class IGameListView;
class SystemData;
class SystemView;
// Used to smoothly transition the camera between multiple views (e.g. from system to system, from gamelist to gamelist).
// Handles transitions between views, e.g. from system to system and from gamelist to gamelist.
// Also sets up the initial gamelists and refreshes and reloads them as required.
class ViewController : public GuiComponent
{
public:
@ -27,8 +37,11 @@ public:
// If a basic view detected a metadata change, it can request to recreate
// the current gamelist view (as it may change to be detailed).
void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false);
inline void reloadGameListView(SystemData* system, bool reloadTheme = false) { reloadGameListView(getGameListView(system).get(), reloadTheme); }
void reloadAll(); // Reload everything with a theme. Used when the "ThemeSet" setting changes.
inline void reloadGameListView(SystemData* system, bool reloadTheme = false)
{ reloadGameListView(getGameListView(system).get(), reloadTheme); }
// Reload everything with a theme.
// Used when the "ThemeSet" setting changes.
void reloadAll();
// Navigation.
void goToNextGameList();
@ -42,22 +55,21 @@ public:
// Plays a nice launch effect and launches the game at the end of it.
// Once the game terminates, plays a return effect.
void launch(FileData* game, Vector3f centerCameraOn = Vector3f(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0));
void launch(FileData* game, Vector3f centerCameraOn =
Vector3f(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0));
bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override;
void render(const Transform4x4f& parentTrans) override;
enum ViewMode
{
enum ViewMode {
NOTHING,
START_SCREEN,
SYSTEM_SELECT,
GAME_LIST
};
enum GameListViewType
{
enum GameListViewType {
AUTOMATIC,
BASIC,
DETAILED,
@ -65,11 +77,11 @@ public:
VIDEO
};
struct State
{
struct State {
ViewMode viewing;
inline SystemData* getSystem() const { assert(viewing == GAME_LIST || viewing == SYSTEM_SELECT); return system; }
inline SystemData* getSystem() const
{ assert(viewing == GAME_LIST || viewing == SYSTEM_SELECT); return system; }
private:
friend ViewController;

View file

@ -1,3 +1,9 @@
//
// BasicGameListView.cpp
//
// Interface that defines a GameListView of the type 'Basic'.
//
#include "views/gamelist/BasicGameListView.h"
#include "utils/FileSystemUtil.h"
@ -7,8 +13,11 @@
#include "Settings.h"
#include "SystemData.h"
BasicGameListView::BasicGameListView(Window* window, FileData* root)
: ISimpleGameListView(window, root), mList(window)
BasicGameListView::BasicGameListView(
Window* window,
FileData* root)
: ISimpleGameListView(window, root),
mList(window)
{
mList.setSize(mSize.x(), mSize.y() * 0.8f);
mList.setPosition(0, mSize.y() * 0.2f);
@ -31,7 +40,7 @@ void BasicGameListView::onFileChanged(FileData* file, FileChangeType change)
{
if (change == FILE_METADATA_CHANGED)
{
// might switch to a detailed view
// Might switch to a detailed view.
ViewController::get()->reloadGameListView(this);
return;
}
@ -43,55 +52,23 @@ void BasicGameListView::populateList(const std::vector<FileData*>& files)
{
mList.clear();
mHeaderText.setText(mRoot->getSystem()->getFullName());
if (files.size() > 0)
{
std::string systemName = mRoot->getSystem()->getName();
bool favoritesFirst = Settings::getInstance()->getBool("FavoritesFirst");
bool showFavoriteIcon = (systemName != "favorites" && systemName != "recent");
if (!showFavoriteIcon)
favoritesFirst = false;
if (favoritesFirst)
{
for (auto file : files)
{
if (!file->getFavorite())
continue;
if (showFavoriteIcon)
mList.add(FAVORITE_GAME_CHAR + " " + file->getName(), file, file->getType() == FOLDER);
else if (file->getType() == FOLDER)
mList.add(FAVORITE_FOLDER_CHAR + " " + file->getName(), file, true);
else
mList.add(file->getName(), file, false);
if (files.size() > 0) {
for (auto it = files.cbegin(); it != files.cend(); it++) {
if ((*it)->getFavorite() &&
mRoot->getSystem()->getName() != "favorites") {
mList.add(FAVORITE_GAME_CHAR + " " + (*it)->getName(),
*it, ((*it)->getType() == FOLDER));
}
else if ((*it)->getType() == FOLDER &&
mRoot->getSystem()->getName() != "collections") {
mList.add(FAVORITE_FOLDER_CHAR + " " + (*it)->getName(), *it, true);
}
else {
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
}
}
for (auto file : files)
{
if (file->getFavorite())
{
if (favoritesFirst)
continue;
if (showFavoriteIcon)
{
mList.add(FAVORITE_GAME_CHAR + " " + file->getName(), file, file->getType() == FOLDER);
continue;
}
}
if (file->getType() == FOLDER)
mList.add(FAVORITE_FOLDER_CHAR + " " + file->getName(), file, true);
else
mList.add(file->getName(), file, false);
}
}
else
{
else {
addPlaceholder();
}
}
@ -103,12 +80,12 @@ FileData* BasicGameListView::getCursor()
void BasicGameListView::setCursor(FileData* cursor)
{
if(!mList.setCursor(cursor) && (!cursor->isPlaceHolder()))
{
if (!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) {
populateList(cursor->getParent()->getChildrenListToDisplay());
mList.setCursor(cursor);
// update our cursor stack in case our cursor just got set to some folder we weren't in before
// Update our cursor stack in case our cursor just
// got set to some folder we weren't in before.
if (mCursorStack.empty() || mCursorStack.top() != cursor->getParent())
{
std::stack<FileData*> tmp;
@ -119,7 +96,7 @@ void BasicGameListView::setCursor(FileData* cursor)
ptr = ptr->getParent();
}
// flip the stack and put it in mCursorStack
// Flip the stack and put it in mCursorStack.
mCursorStack = std::stack<FileData*>();
while (!tmp.empty())
{
@ -132,8 +109,9 @@ void BasicGameListView::setCursor(FileData* cursor)
void BasicGameListView::addPlaceholder()
{
// empty list - add a placeholder
FileData* placeholder = new FileData(PLACEHOLDER, "<No Entries Found>", this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem());
// Empty list - add a placeholder.
FileData* placeholder = new FileData(PLACEHOLDER, "<No Entries Found>",
this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem());
mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER));
}
@ -154,31 +132,31 @@ void BasicGameListView::launch(FileData* game)
void BasicGameListView::remove(FileData *game, bool deleteFile)
{
// Actually delete the file on the filesystem.
if (deleteFile)
Utils::FileSystem::removeFile(game->getPath()); // actually delete the file on the filesystem
Utils::FileSystem::removeFile(game->getPath());
FileData* parent = game->getParent();
if (getCursor() == game) // Select next element in list, or prev if none
{
// Select next element in list, or previous if none.
if (getCursor() == game) {
std::vector<FileData*> siblings = parent->getChildrenListToDisplay();
auto gameIter = std::find(siblings.cbegin(), siblings.cend(), game);
unsigned int gamePos = (int)std::distance(siblings.cbegin(), gameIter);
if (gameIter != siblings.cend())
{
if (gameIter != siblings.cend()) {
if ((gamePos + 1) < siblings.size())
{
setCursor(siblings.at(gamePos + 1));
} else if (gamePos > 1) {
else if (gamePos > 1)
setCursor(siblings.at(gamePos - 1));
}
}
}
mList.remove(game);
if (mList.size() == 0)
{
addPlaceholder();
}
delete game; // remove before repopulating (removes from parent)
onFileChanged(parent, FILE_REMOVED); // update the view, with game removed
// Remove before repopulating (removes from parent), then update the view.
delete game;
onFileChanged(parent, FILE_REMOVED);
}
std::vector<HelpPrompt> BasicGameListView::getHelpPrompts()

View file

@ -1,3 +1,9 @@
//
// BasicGameListView.h
//
// Interface that defines a GameListView of the type 'basic'.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_BASIC_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_BASIC_GAME_LIST_VIEW_H
@ -10,7 +16,7 @@ class BasicGameListView : public ISimpleGameListView
public:
BasicGameListView(Window* window, FileData* root);
// Called when a FileData* is added, has its metadata changed, or is removed
// Called when a FileData* is added, has its metadata changed, or is removed.
virtual void onFileChanged(FileData* file, FileChangeType change);
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme);

View file

@ -1,24 +1,44 @@
//
// DetailedGameListView.cpp
//
// Interface that defines a GameListView of the type 'detailed'.
//
#include "views/gamelist/DetailedGameListView.h"
#include "animations/LambdaAnimation.h"
#include "views/ViewController.h"
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
DetailedGameListView::DetailedGameListView(
Window* window,
FileData* root)
: BasicGameListView(window, root),
mDescContainer(window),
mDescription(window),
mThumbnail(window),
mMarquee(window),
mImage(window),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
mLblRating(window),
mLblReleaseDate(window),
mLblDeveloper(window),
mLblPublisher(window),
mLblGenre(window),
mLblPlayers(window),
mLblLastPlayed(window),
mLblPlayCount(window),
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window),
mRating(window),
mReleaseDate(window),
mDeveloper(window),
mPublisher(window),
mGenre(window),
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window)
{
//mHeaderImage.setPosition(mSize.x() * 0.25f, 0);
const float padding = 0.01f;
mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y());
@ -26,7 +46,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
// Thumbnail
// Thumbnail.
mThumbnail.setOrigin(0.5f, 0.5f);
mThumbnail.setPosition(2.0f, 2.0f);
mThumbnail.setVisible(false);
@ -34,23 +54,23 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
mThumbnail.setDefaultZIndex(25);
addChild(&mThumbnail);
// Marquee
// Marquee.
mMarquee.setOrigin(0.5f, 0.5f);
// Default to off the screen
// Default to off the screen.
mMarquee.setPosition(2.0f, 2.0f);
mMarquee.setVisible(false);
mMarquee.setMaxSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.18f);
mMarquee.setDefaultZIndex(35);
addChild(&mMarquee);
// image
// Image.
mImage.setOrigin(0.5f, 0.5f);
mImage.setPosition(mSize.x() * 0.25f, mList.getPosition().y() + mSize.y() * 0.2125f);
mImage.setMaxSize(mSize.x() * (0.50f - 2*padding), mSize.y() * 0.4f);
mImage.setDefaultZIndex(30);
addChild(&mImage);
// metadata labels + values
// Metadata labels + values.
mLblRating.setText("Rating: ");
addChild(&mLblRating);
addChild(&mRating);
@ -85,7 +105,8 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() -
mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer);
@ -94,7 +115,6 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescContainer.addChild(&mDescription);
initMDLabels();
initMDValues();
updateInfoPanel();
@ -105,9 +125,12 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
BasicGameListView::onThemeChanged(theme);
using namespace ThemeFlags;
mThumbnail.applyTheme(theme, getName(), "md_thumbnail", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mThumbnail.applyTheme(theme, getName(), "md_thumbnail",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mName.applyTheme(theme, getName(), "md_name", ALL);
initMDLabels();
@ -119,10 +142,7 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
};
for (unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
@ -133,13 +153,13 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
};
for (unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mDescription.applyTheme(theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
sortChildren();
}
@ -156,15 +176,14 @@ void DetailedGameListView::initMDLabels()
const float colSize = (mSize.x() * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y();
for(unsigned int i = 0; i < components.size(); i++)
{
for (unsigned int i = 0; i < components.size(); i++) {
const unsigned int row = i % rowCount;
Vector3f pos(0.0f, 0.0f, 0.0f);
if(row == 0)
{
if (row == 0) {
pos = start + Vector3f(colSize * (i / rowCount), 0, 0);
}else{
// work from the last component
}
else {
// Work from the last component.
GuiComponent* lc = components[i-1];
pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0);
}
@ -193,20 +212,22 @@ void DetailedGameListView::initMDValues()
float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2;
for(unsigned int i = 0; i < labels.size(); i++)
{
for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2;
values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
values[i]->setDefaultZIndex(40);
float testBot = values[i]->getPosition().y() + values[i]->getSize().y();
if (testBot > bottom)
bottom = testBot;
}
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() -
mDescContainer.getPosition().y());
}
void DetailedGameListView::updateInfoPanel()
@ -214,12 +235,12 @@ void DetailedGameListView::updateInfoPanel()
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? NULL : mList.getSelected();
bool fadingOut;
if(file == NULL)
{
if (file == nullptr) {
//mImage.setImage("");
//mDescription.setText("");
fadingOut = true;
}else{
}
else {
mThumbnail.setImage(file->getThumbnailPath());
mMarquee.setImage(file->getMarqueePath());
mImage.setImage(file->getImagePath());
@ -234,8 +255,7 @@ void DetailedGameListView::updateInfoPanel()
mPlayers.setValue(file->metadata.get("players"));
mName.setValue(file->metadata.get("name"));
if(file->getType() == GAME)
{
if (file->getType() == GAME) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
@ -252,18 +272,13 @@ void DetailedGameListView::updateInfoPanel()
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for(auto it = comps.cbegin(); it != comps.cend(); it++)
{
for (auto it = comps.cbegin(); it != comps.cend(); it++) {
GuiComponent* comp = *it;
// an animation is playing
// then animate if reverse != fadingOut
// an animation is not playing
// then animate if opacity != our target opacity
// An animation is playing, then animate if reverse != fadingOut
// An animation is not playing, then animate if opacity != our target opacity
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255)))
{
auto func = [comp](float t)
{
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity((unsigned char)(Math::lerp(0.0f, 1.0f, t)*255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);

View file

@ -1,3 +1,9 @@
//
// DetailedGameListView.h
//
// Interface that defines a GameListView of the type 'detailed'.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_DETAILED_GAME_LIST_VIEW_H
@ -28,7 +34,14 @@ private:
ImageComponent mMarquee;
ImageComponent mImage;
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
RatingComponent mRating;
DateTimeComponent mReleaseDate;

View file

@ -1,3 +1,9 @@
//
// GridGameListView.cpp
//
// Interface that defines a GameListView of the type 'grid'.
//
#include "views/gamelist/GridGameListView.h"
#include "animations/LambdaAnimation.h"
@ -12,24 +18,42 @@
#endif
#include "components/VideoVlcComponent.h"
GridGameListView::GridGameListView(Window* window, FileData* root) :
ISimpleGameListView(window, root),
mGrid(window), mMarquee(window),
GridGameListView::GridGameListView(
Window* window,
FileData* root)
: ISimpleGameListView(window, root),
mGrid(window),
mMarquee(window),
mImage(window),
mVideo(nullptr),
mVideoPlaying(false),
mDescContainer(window), mDescription(window),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
mDescContainer(window),
mDescription(window),
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window),
mLblRating(window),
mLblReleaseDate(window),
mLblDeveloper(window),
mLblPublisher(window),
mLblGenre(window),
mLblPlayers(window),
mLblLastPlayed(window),
mLblPlayCount(window),
mRating(window),
mReleaseDate(window),
mDeveloper(window),
mPublisher(window),
mGenre(window),
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window)
{
const float padding = 0.01f;
// Create the correct type of video window
// Create the correct type of video window.
#ifdef _RPI_
if (Settings::getInstance()->getBool("VideoOmxPlayer"))
mVideo = new VideoPlayerComponent(window, "");
@ -46,7 +70,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) :
populateList(root->getChildrenListToDisplay());
// metadata labels + values
// Metadata labels + values.
mLblRating.setText("Rating: ");
addChild(&mLblRating);
addChild(&mRating);
@ -81,7 +105,8 @@ GridGameListView::GridGameListView(Window* window, FileData* root) :
addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() -
mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer);
@ -147,8 +172,10 @@ std::string GridGameListView::getQuickSystemSelectLeftButton()
bool GridGameListView::input(InputConfig* config, Input input)
{
if (input.value == 0 and (config->isMappedLike("left", input) || config->isMappedLike("right", input)
|| (config->isMappedLike("up", input)) || (config->isMappedLike("down", input)) ))
if (input.value == 0 and (config->isMappedLike("left", input) ||
config->isMappedLike("right", input) ||
(config->isMappedLike("up", input)) ||
(config->isMappedLike("down", input)) ))
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
if (config->isMappedLike("left", input) || config->isMappedLike("right", input))
@ -167,7 +194,7 @@ const std::string GridGameListView::getImagePath(FileData* file)
else if (src == ImageSource::MARQUEE)
return file->getMarqueePath();
// If no thumbnail was found, then use the image media type
// If no thumbnail was found, then use the image media type.
if (file->getThumbnailPath() == "");
return file->getImagePath();
@ -178,18 +205,13 @@ void GridGameListView::populateList(const std::vector<FileData*>& files)
{
mGrid.clear();
mHeaderText.setText(mRoot->getSystem()->getFullName());
if (files.size() > 0)
{
if (files.size() > 0) {
for (auto it = files.cbegin(); it != files.cend(); it++)
{
mGrid.add((*it)->getName(), getImagePath(*it), *it);
}
}
else
{
addPlaceholder();
}
}
void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
@ -199,9 +221,12 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mGrid.applyTheme(theme, getName(), "gamegrid", ALL);
mName.applyTheme(theme, getName(), "md_name", ALL);
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video",
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
@ -212,10 +237,7 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
};
for (unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
@ -226,15 +248,16 @@ void GridGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
};
for (unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mDescription.applyTheme(theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
// Repopulate list in case new theme is displaying a different image. Preserve selection.
// Repopulate list in case a new theme is displaying a different image.
// Preserve selection.
FileData* file = mGrid.getSelected();
populateList(mRoot->getChildrenListToDisplay());
mGrid.setCursor(file);
@ -254,15 +277,14 @@ void GridGameListView::initMDLabels()
const float colSize = (mSize.x() * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y();
for(unsigned int i = 0; i < components.size(); i++)
{
for (unsigned int i = 0; i < components.size(); i++) {
const unsigned int row = i % rowCount;
Vector3f pos(0.0f, 0.0f, 0.0f);
if(row == 0)
{
if (row == 0) {
pos = start + Vector3f(colSize * (i / rowCount), 0, 0);
}else{
// work from the last component
}
else {
// Work from the last component.
GuiComponent* lc = components[i-1];
pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0);
}
@ -291,10 +313,10 @@ void GridGameListView::initMDValues()
float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2;
for(unsigned int i = 0; i < labels.size(); i++)
{
for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2;
values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
values[i]->setDefaultZIndex(40);
@ -304,23 +326,24 @@ void GridGameListView::initMDValues()
}
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() -
mDescContainer.getPosition().y());
}
void GridGameListView::updateInfoPanel()
{
FileData* file = (mGrid.size() == 0 || mGrid.isScrolling()) ? NULL : mGrid.getSelected();
FileData* file = (mGrid.size() == 0 || mGrid.isScrolling()) ? nullptr : mGrid.getSelected();
bool fadingOut;
if(file == NULL)
if (file == nullptr)
{
mVideo->setVideo("");
mVideo->setImage("");
mVideoPlaying = false;
//mDescription.setText("");
fadingOut = true;
}else{
// Temporary fix to disable only audio from playing
// if (!mVideo->setVideo(file->getVideoPath()))
// {
// mVideo->setDefaultVideo();
@ -342,8 +365,7 @@ void GridGameListView::updateInfoPanel()
mPlayers.setValue(file->metadata.get("players"));
mName.setValue(file->metadata.get("name"));
if(file->getType() == GAME)
{
if (file->getType() == GAME) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
@ -360,18 +382,13 @@ void GridGameListView::updateInfoPanel()
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for(auto it = comps.cbegin(); it != comps.cend(); it++)
{
for (auto it = comps.cbegin(); it != comps.cend(); it++) {
GuiComponent* comp = *it;
// an animation is playing
// then animate if reverse != fadingOut
// an animation is not playing
// then animate if opacity != our target opacity
// An animation is playing, then animate if reverse != fadingOut
// An animation is not playing, then animate if opacity != our target opacity
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255)))
{
auto func = [comp](float t)
{
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity((unsigned char)(Math::lerp(0.0f, 1.0f, t)*255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
@ -381,8 +398,9 @@ void GridGameListView::updateInfoPanel()
void GridGameListView::addPlaceholder()
{
// empty grid - add a placeholder
FileData* placeholder = new FileData(PLACEHOLDER, "<No Entries Found>", this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem());
// Empty grid - add a placeholder.
FileData* placeholder = new FileData(PLACEHOLDER, "<No Entries Found>",
this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem());
mGrid.add(placeholder->getName(), "", placeholder);
}
@ -396,52 +414,46 @@ void GridGameListView::launch(FileData* game)
if (mMarquee.hasImage() &&
(mMarquee.getPosition().x() < screenWidth && mMarquee.getPosition().x() > 0.0f &&
mMarquee.getPosition().y() < screenHeight && mMarquee.getPosition().y() > 0.0f))
{
target = Vector3f(mMarquee.getCenter().x(), mMarquee.getCenter().y(), 0);
}
else if (mImage.hasImage() &&
(mImage.getPosition().x() < screenWidth && mImage.getPosition().x() > 2.0f &&
mImage.getPosition().y() < screenHeight && mImage.getPosition().y() > 2.0f))
{
target = Vector3f(mImage.getCenter().x(), mImage.getCenter().y(), 0);
}
else if (mVideo->getPosition().x() < screenWidth && mVideo->getPosition().x() > 0.0f &&
mVideo->getPosition().y() < screenHeight && mVideo->getPosition().y() > 0.0f)
{
target = Vector3f(mVideo->getCenter().x(), mVideo->getCenter().y(), 0);
}
ViewController::get()->launch(game, target);
}
void GridGameListView::remove(FileData *game, bool deleteFile)
{
// Actually delete the file on the filesystem.
if (deleteFile)
Utils::FileSystem::removeFile(game->getPath()); // actually delete the file on the filesystem
Utils::FileSystem::removeFile(game->getPath());
FileData* parent = game->getParent();
if (getCursor() == game) // Select next element in list, or prev if none
{
// Select next element in list, or previous if none.
if (getCursor() == game) {
std::vector<FileData*> siblings = parent->getChildrenListToDisplay();
auto gameIter = std::find(siblings.cbegin(), siblings.cend(), game);
int gamePos = (int)std::distance(siblings.cbegin(), gameIter);
if (gameIter != siblings.cend())
{
if (gameIter != siblings.cend()) {
if ((gamePos + 1) < (int)siblings.size())
{
setCursor(siblings.at(gamePos + 1));
} else if ((gamePos - 1) > 0) {
else if ((gamePos - 1) > 0)
setCursor(siblings.at(gamePos - 1));
}
}
}
mGrid.remove(game);
if (mGrid.size() == 0)
{
addPlaceholder();
}
delete game; // remove before repopulating (removes from parent)
onFileChanged(parent, FILE_REMOVED); // update the view, with game removed
// Remove before repopulating (removes from parent).
// Update the view, with game removed.
delete game;
onFileChanged(parent, FILE_REMOVED);
}
std::vector<TextComponent*> GridGameListView::getMDLabels()
@ -485,8 +497,7 @@ std::vector<HelpPrompt> GridGameListView::getHelpPrompts()
prompts.push_back(HelpPrompt("select", "options"));
if (mRoot->getSystem()->isGameSystem())
prompts.push_back(HelpPrompt("x", "random"));
if(mRoot->getSystem()->isGameSystem() && !UIModeController::getInstance()->isUIModeKid())
{
if (mRoot->getSystem()->isGameSystem() && !UIModeController::getInstance()->isUIModeKid()) {
std::string prompt = CollectionSystemManager::get()->getEditingCollection();
prompts.push_back(HelpPrompt("y", prompt));
}

View file

@ -1,3 +1,9 @@
//
// GridGameListView.h
//
// Interface that defines a GameListView of the type 'grid'.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_GRID_GAME_LIST_VIEW_H
@ -46,7 +52,14 @@ private:
void initMDLabels();
void initMDValues();
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
ImageComponent mMarquee;
VideoComponent* mVideo;

View file

@ -1,3 +1,9 @@
//
// IGameListView.cpp
//
// Interface that defines the minimum for a GameListView.
//
#include "views/gamelist/IGameListView.h"
#include "guis/GuiGamelistOptions.h"
@ -8,16 +14,17 @@
bool IGameListView::input(InputConfig* config, Input input)
{
// select to open GuiGamelistOptions
if(!UIModeController::getInstance()->isUIModeKid() && config->isMappedTo("select", input) && input.value)
{
// Select button opens GuiGamelistOptions.
if (!UIModeController::getInstance()->isUIModeKid() &&
config->isMappedTo("select", input) && input.value) {
mWindow->pushGui(new GuiGamelistOptions(mWindow, this->mRoot->getSystem()));
return true;
// Ctrl-R to reload a view when debugging
}else if(Settings::getInstance()->getBool("Debug") && config->getDeviceId() == DEVICE_KEYBOARD &&
(SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) && input.id == SDLK_r && input.value != 0)
{
}
// Ctrl-R reloads the view when debugging.
else if (Settings::getInstance()->getBool("Debug") &&
config->getDeviceId() == DEVICE_KEYBOARD &&
(SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) &&
input.id == SDLK_r && input.value != 0) {
LOG(LogDebug) << "reloading view";
ViewController::get()->reloadGameListView(this, true);
return true;
@ -46,8 +53,10 @@ void IGameListView::render(const Transform4x4f& parentTrans)
float scaleX = trans.r0().x();
float scaleY = trans.r1().y();
Vector2i pos((int)Math::round(trans.translation()[0]), (int)Math::round(trans.translation()[1]));
Vector2i size((int)Math::round(mSize.x() * scaleX), (int)Math::round(mSize.y() * scaleY));
Vector2i pos((int)Math::round(trans.translation()[0]),
(int)Math::round(trans.translation()[1]));
Vector2i size((int)Math::round(mSize.x() * scaleX),
(int)Math::round(mSize.y() * scaleY));
Renderer::pushClipRect(pos, size);
renderChildren(trans);

View file

@ -1,3 +1,9 @@
//
// IGameListView.h
//
// Interface that defines the minimum for a GameListView.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_IGAME_LIST_VIEW_H
@ -13,12 +19,18 @@ class Window;
class IGameListView : public GuiComponent
{
public:
IGameListView(Window* window, FileData* root) : GuiComponent(window), mRoot(root)
{ setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); }
IGameListView(
Window* window,
FileData* root)
: GuiComponent(window),
mRoot(root)
{ setSize((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight()); }
virtual ~IGameListView() {}
// Called when a new file is added, a file is removed, a file's metadata changes, or a file's children are sorted.
// Called when a new file is added, a file is removed, a file's metadata changes,
// or a file's children are sorted.
// NOTE: FILE_SORTED is only reported for the topmost FileData, where the sort started.
// Since sorts are recursive, that FileData's children probably changed too.
virtual void onFileChanged(FileData* file, FileChangeType change) = 0;

View file

@ -1,3 +1,9 @@
//
// ISimpleGameListView.cpp
//
// Interface that defines a simple GameListView.
//
#include "views/gamelist/ISimpleGameListView.h"
#include "views/UIModeController.h"
@ -7,8 +13,13 @@
#include "Sound.h"
#include "SystemData.h"
ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGameListView(window, root),
mHeaderText(window), mHeaderImage(window), mBackground(window)
ISimpleGameListView::ISimpleGameListView(
Window* window,
FileData* root)
: IGameListView(window, root),
mHeaderText(window),
mHeaderImage(window),
mBackground(window)
{
mHeaderText.setText("Logo Text");
mHeaderText.setSize(mSize.x(), 0);
@ -35,7 +46,7 @@ void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme
mHeaderImage.applyTheme(theme, getName(), "logo", ALL);
mHeaderText.applyTheme(theme, getName(), "logoText", ALL);
// Remove old theme extras
// Remove old theme extras.
for (auto extra : mThemeExtras)
{
removeChild(extra);
@ -43,18 +54,16 @@ void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme
}
mThemeExtras.clear();
// Add new theme extras
// Add new theme extras.
mThemeExtras = ThemeData::makeExtras(theme, getName(), mWindow);
for (auto extra : mThemeExtras)
{
addChild(extra);
}
if(mHeaderImage.hasImage())
{
if (mHeaderImage.hasImage()) {
removeChild(&mHeaderText);
addChild(&mHeaderImage);
}else{
}
else {
addChild(&mHeaderText);
removeChild(&mHeaderImage);
}
@ -62,8 +71,8 @@ void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme
void ISimpleGameListView::onFileChanged(FileData* /*file*/, FileChangeType /*change*/)
{
// we could be tricky here to be efficient;
// but this shouldn't happen very often so we'll just always repopulate
// We could be tricky here to be efficient;
// but this shouldn't happen very often so we'll just always repopulate.
FileData* cursor = getCursor();
if (!cursor->isPlaceHolder()) {
populateList(cursor->getParent()->getChildrenListToDisplay());
@ -80,18 +89,15 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
{
std::shared_ptr<Sound> soundfile;
if(input.value != 0)
{
if(config->isMappedTo("a", input))
{
if (input.value != 0) {
if (config->isMappedTo("a", input)) {
FileData* cursor = getCursor();
if(cursor->getType() == GAME)
{
if (cursor->getType() == GAME) {
launch(cursor);
}else{
// it's a folder
if(cursor->getChildren().size() > 0)
{
}
else {
// It's a folder.
if (cursor->getChildren().size() > 0) {
mCursorStack.push(cursor);
populateList(cursor->getChildrenListToDisplay());
FileData* cursor = getCursor();
@ -100,14 +106,14 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
}
return true;
}else if(config->isMappedTo("b", input))
{
if(mCursorStack.size())
{
}
else if (config->isMappedTo("b", input)) {
if (mCursorStack.size()) {
populateList(mCursorStack.top()->getParent()->getChildren());
setCursor(mCursorStack.top());
mCursorStack.pop();
}else{
}
else {
navigationsounds.playThemeNavigationSound(BACKSOUND);
onFocusLost();
SystemData* systemToView = getCursor()->getSystem();
@ -119,57 +125,40 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
}
return true;
}else if(config->isMappedLike(getQuickSystemSelectRightButton(), input))
{
if(Settings::getInstance()->getBool("QuickSystemSelect"))
{
}
else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect")) {
onFocusLost();
ViewController::get()->goToNextGameList();
return true;
}
}else if(config->isMappedLike(getQuickSystemSelectLeftButton(), input))
{
if(Settings::getInstance()->getBool("QuickSystemSelect"))
{
}
else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) {
if (Settings::getInstance()->getBool("QuickSystemSelect")) {
onFocusLost();
ViewController::get()->goToPrevGameList();
return true;
}
}else if (config->isMappedTo("x", input))
{
if (mRoot->getSystem()->isGameSystem())
{
// go to random system game
}
else if (config->isMappedTo("x", input)) {
if (mRoot->getSystem()->isGameSystem()) {
// Go to random system game.
navigationsounds.playThemeNavigationSound(SCROLLSOUND);
FileData* randomGame = getCursor()->getSystem()->getRandomGame();
if (randomGame)
{
setCursor(randomGame);
}
return true;
}
}else if (config->isMappedTo("y", input) && !UIModeController::getInstance()->isUIModeKid())
{
if(mRoot->getSystem()->isGameSystem())
{
}
else if (config->isMappedTo("y", input) &&
!UIModeController::getInstance()->isUIModeKid()) {
if (mRoot->getSystem()->isGameSystem()) {
navigationsounds.playThemeNavigationSound(FAVORITESOUND);
if (CollectionSystemManager::get()->toggleGameInCollection(getCursor()))
{
return true;
}
}
}
}
return IGameListView::input(config, input);
}

View file

@ -1,3 +1,9 @@
//
// ISimpleGameListView.h
//
// Interface that defines a simple GameListView.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_ISIMPLE_GAME_LIST_VIEW_H
@ -11,9 +17,11 @@ class ISimpleGameListView : public IGameListView
{
public:
ISimpleGameListView(Window* window, FileData* root);
virtual ~ISimpleGameListView() {}
// Called when a new file is added, a file is removed, a file's metadata changes, or a file's children are sorted.
// Called when a new file is added, a file is removed, a file's metadata changes,
// or a file's children are sorted.
// NOTE: FILE_SORTED is only reported for the topmost FileData, where the sort started.
// Since sorts are recursive, that FileData's children probably changed too.
virtual void onFileChanged(FileData* file, FileChangeType change);

View file

@ -1,3 +1,9 @@
//
// VideoGameListView.cpp
//
// Interface that defines a GameListView of the type 'video'.
//
#include "views/gamelist/VideoGameListView.h"
#include "animations/LambdaAnimation.h"
@ -11,25 +17,41 @@
#include "Settings.h"
#endif
VideoGameListView::VideoGameListView(Window* window, FileData* root) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
VideoGameListView::VideoGameListView(
Window* window,
FileData* root)
: BasicGameListView(window, root),
mDescContainer(window),
mDescription(window),
mThumbnail(window),
mMarquee(window),
mImage(window),
mVideo(nullptr),
mVideoPlaying(false),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
mLblRating(window),
mLblReleaseDate(window),
mLblDeveloper(window),
mLblPublisher(window),
mLblGenre(window),
mLblPlayers(window),
mLblLastPlayed(window),
mLblPlayCount(window),
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window),
mRating(window),
mReleaseDate(window),
mDeveloper(window),
mPublisher(window),
mGenre(window),
mPlayers(window),
mLastPlayed(window),
mPlayCount(window),
mName(window)
{
const float padding = 0.01f;
// Create the correct type of video window
// Create the correct type of video window.
#ifdef _RPI_
if (Settings::getInstance()->getBool("VideoOmxPlayer"))
mVideo = new VideoPlayerComponent(window, "");
@ -44,7 +66,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
// Thumbnail
// Thumbnail.
mThumbnail.setOrigin(0.5f, 0.5f);
mThumbnail.setPosition(2.0f, 2.0f);
mThumbnail.setVisible(false);
@ -52,30 +74,30 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
mThumbnail.setDefaultZIndex(35);
addChild(&mThumbnail);
// Marquee
// Marquee.
mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f);
mMarquee.setMaxSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.18f);
mMarquee.setDefaultZIndex(35);
addChild(&mMarquee);
// Image
// Image.
mImage.setOrigin(0.5f, 0.5f);
// Default to off the screen
// Default to off the screen.
mImage.setPosition(mSize.x() * 0.25f, mList.getPosition().y() + mSize.y() * 0.2125f);
mImage.setVisible(false);
mImage.setMaxSize(mSize.x() * (0.50f - 2*padding), mSize.y() * 0.4f);
mImage.setDefaultZIndex(30);
addChild(&mImage);
// video
// Video.
mVideo->setOrigin(0.5f, 0.5f);
mVideo->setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f);
mVideo->setSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.4f);
mVideo->setDefaultZIndex(30);
addChild(mVideo);
// metadata labels + values
// Metadata labels + values.
mLblRating.setText("Rating: ");
addChild(&mLblRating);
addChild(&mRating);
@ -110,7 +132,8 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
addChild(&mName);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() -
mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true);
mDescContainer.setDefaultZIndex(40);
addChild(&mDescContainer);
@ -133,10 +156,14 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
BasicGameListView::onThemeChanged(theme);
using namespace ThemeFlags;
mThumbnail.applyTheme(theme, getName(), "md_thumbnail", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE);
mThumbnail.applyTheme(theme, getName(), "md_thumbnail",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mMarquee.applyTheme(theme, getName(), "md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImage.applyTheme(theme, getName(), "md_image",
POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mVideo->applyTheme(theme, getName(), "md_video",
POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE);
mName.applyTheme(theme, getName(), "md_name", ALL);
initMDLabels();
@ -148,10 +175,7 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
};
for (unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
@ -162,13 +186,13 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
};
for (unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescContainer.applyTheme(theme, getName(), "md_description",
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
mDescription.applyTheme(theme, getName(), "md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
sortChildren();
}
@ -185,15 +209,14 @@ void VideoGameListView::initMDLabels()
const float colSize = (mSize.x() * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y();
for(unsigned int i = 0; i < components.size(); i++)
{
for (unsigned int i = 0; i < components.size(); i++) {
const unsigned int row = i % rowCount;
Vector3f pos(0.0f, 0.0f, 0.0f);
if(row == 0)
{
if (row == 0) {
pos = start + Vector3f(colSize * (i / rowCount), 0, 0);
}else{
// work from the last component
}
else {
// Work from the last component.
GuiComponent* lc = components[i-1];
pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0);
}
@ -222,10 +245,10 @@ void VideoGameListView::initMDValues()
float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2;
for(unsigned int i = 0; i < labels.size(); i++)
{
for (unsigned int i = 0; i < labels.size(); i++) {
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2;
values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setPosition(labels[i]->getPosition() +
Vector3f(labels[i]->getSize().x(),heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
values[i]->setDefaultZIndex(40);
@ -235,11 +258,10 @@ void VideoGameListView::initMDValues()
}
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() -
mDescContainer.getPosition().y());
}
void VideoGameListView::updateInfoPanel()
{
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? NULL : mList.getSelected();
@ -247,8 +269,7 @@ void VideoGameListView::updateInfoPanel()
Utils::FileSystem::removeFile(getTitlePath());
bool fadingOut;
if(file == NULL)
{
if (file == nullptr) {
mVideo->setVideo("");
mVideo->setImage("");
mVideoPlaying = false;
@ -256,14 +277,13 @@ void VideoGameListView::updateInfoPanel()
//mDescription.setText("");
fadingOut = true;
}else{
if (!mVideo->setVideo(file->getVideoPath()))
{
mVideo->setDefaultVideo();
}
else {
if (!mVideo->setVideo(file->getVideoPath()))
mVideo->setDefaultVideo();
mVideoPlaying = true;
// mVideo->setImage(file->getThumbnailPath());
mVideo->setImage(file->getImagePath());
mThumbnail.setImage(file->getThumbnailPath());
mMarquee.setImage(file->getMarqueePath());
@ -280,8 +300,7 @@ void VideoGameListView::updateInfoPanel()
mPlayers.setValue(file->metadata.get("players"));
mName.setValue(file->metadata.get("name"));
if(file->getType() == GAME)
{
if (file->getType() == GAME) {
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
@ -299,18 +318,13 @@ void VideoGameListView::updateInfoPanel()
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.cend(), labels.cbegin(), labels.cend());
for(auto it = comps.cbegin(); it != comps.cend(); it++)
{
for (auto it = comps.cbegin(); it != comps.cend(); it++) {
GuiComponent* comp = *it;
// an animation is playing
// then animate if reverse != fadingOut
// an animation is not playing
// then animate if opacity != our target opacity
// An animation is playing, then animate if reverse != fadingOut
// An animation is not playing, then animate if opacity != our target opacity
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255)))
{
auto func = [comp](float t)
{
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity((unsigned char)(Math::lerp(0.0f, 1.0f, t)*255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
@ -328,32 +342,28 @@ void VideoGameListView::launch(FileData* game)
if (mMarquee.hasImage() &&
(mMarquee.getPosition().x() < screenWidth && mMarquee.getPosition().x() > 0.0f &&
mMarquee.getPosition().y() < screenHeight && mMarquee.getPosition().y() > 0.0f))
{
target = Vector3f(mMarquee.getCenter().x(), mMarquee.getCenter().y(), 0);
}
else if (mThumbnail.hasImage() &&
(mThumbnail.getPosition().x() < screenWidth && mThumbnail.getPosition().x() > 2.0f &&
mThumbnail.getPosition().y() < screenHeight && mThumbnail.getPosition().y() > 2.0f))
{
target = Vector3f(mThumbnail.getCenter().x(), mThumbnail.getCenter().y(), 0);
}
else if (mImage.hasImage() &&
(mImage.getPosition().x() < screenWidth && mImage.getPosition().x() > 2.0f &&
mImage.getPosition().y() < screenHeight && mImage.getPosition().y() > 2.0f))
{
target = Vector3f(mImage.getCenter().x(), mImage.getCenter().y(), 0);
}
else if (mHeaderImage.hasImage() &&
(mHeaderImage.getPosition().x() < screenWidth && mHeaderImage.getPosition().x() > 0.0f &&
mHeaderImage.getPosition().y() < screenHeight && mHeaderImage.getPosition().y() > 0.0f))
{
(mHeaderImage.getPosition().x() < screenWidth &&
mHeaderImage.getPosition().x() > 0.0f &&
mHeaderImage.getPosition().y() < screenHeight &&
mHeaderImage.getPosition().y() > 0.0f))
target = Vector3f(mHeaderImage.getCenter().x(), mHeaderImage.getCenter().y(), 0);
}
else if (mVideo->getPosition().x() < screenWidth && mVideo->getPosition().x() > 0.0f &&
mVideo->getPosition().y() < screenHeight && mVideo->getPosition().y() > 0.0f)
{
target = Vector3f(mVideo->getCenter().x(), mVideo->getCenter().y(), 0);
}
ViewController::get()->launch(game, target);
}

View file

@ -1,3 +1,9 @@
//
// VideoGameListView.h
//
// Interface that defines a GameListView of the type 'video'.
//
#pragma once
#ifndef ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H
#define ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H
@ -36,7 +42,14 @@ private:
VideoComponent* mVideo;
ImageComponent mImage;
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;
TextComponent mLblRating;
TextComponent mLblReleaseDate;
TextComponent mLblDeveloper;
TextComponent mLblPublisher;
TextComponent mLblGenre;
TextComponent mLblPlayers;
TextComponent mLblLastPlayed;
TextComponent mLblPlayCount;
RatingComponent mRating;
DateTimeComponent mReleaseDate;

View file

@ -1,3 +1,9 @@
//
// GuiComponent.cpp
//
// Basic GUI component handling such as placement, rotation, Z-order, rendering and animation.
//
#include "GuiComponent.h"
#include "animations/Animation.h"
@ -8,9 +14,17 @@
#include "Window.h"
#include <algorithm>
GuiComponent::GuiComponent(Window* window) : mWindow(window), mParent(NULL), mOpacity(255),
mPosition(Vector3f::Zero()), mOrigin(Vector2f::Zero()), mRotationOrigin(0.5, 0.5),
mSize(Vector2f::Zero()), mTransform(Transform4x4f::Identity()), mIsProcessing(false), mVisible(true)
GuiComponent::GuiComponent(Window* window)
: mWindow(window),
mParent(NULL),
mOpacity(255),
mPosition(Vector3f::Zero()),
mOrigin(Vector2f::Zero()),
mRotationOrigin(0.5, 0.5),
mSize(Vector2f::Zero()),
mTransform(Transform4x4f::Identity()),
mIsProcessing(false),
mVisible(true)
{
for (unsigned char i = 0; i < MAX_ANIMATIONS; i++)
mAnimationMap[i] = NULL;
@ -31,8 +45,7 @@ GuiComponent::~GuiComponent()
bool GuiComponent::input(InputConfig* config, Input input)
{
for(unsigned int i = 0; i < getChildCount(); i++)
{
for (unsigned int i = 0; i < getChildCount(); i++) {
if (getChild(i)->input(config, input))
return true;
}
@ -49,10 +62,8 @@ void GuiComponent::updateSelf(int deltaTime)
void GuiComponent::updateChildren(int deltaTime)
{
for (unsigned int i = 0; i < getChildCount(); i++)
{
getChild(i)->update(deltaTime);
}
}
void GuiComponent::update(int deltaTime)
{
@ -72,10 +83,8 @@ void GuiComponent::render(const Transform4x4f& parentTrans)
void GuiComponent::renderChildren(const Transform4x4f& transform) const
{
for (unsigned int i = 0; i < getChildCount(); i++)
{
getChild(i)->render(transform);
}
}
Vector3f GuiComponent::getPosition() const
{
@ -192,16 +201,12 @@ void GuiComponent::removeChild(GuiComponent* cmp)
return;
if (cmp->getParent() != this)
{
LOG(LogError) << "Tried to remove child from incorrect parent!";
}
cmp->setParent(NULL);
for(auto i = mChildren.cbegin(); i != mChildren.cend(); i++)
{
if(*i == cmp)
{
for (auto i = mChildren.cbegin(); i != mChildren.cend(); i++) {
if (*i == cmp) {
mChildren.erase(i);
return;
}
@ -249,38 +254,34 @@ void GuiComponent::setOpacity(unsigned char opacity)
{
mOpacity = opacity;
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++)
{
(*it)->setOpacity(opacity);
}
}
const Transform4x4f& GuiComponent::getTransform()
{
mTransform = Transform4x4f::Identity();
mTransform.translate(mPosition);
if (mScale != 1.0)
{
mTransform.scale(mScale);
}
if (mRotation != 0.0)
{
if (mRotation != 0.0) {
// Calculate offset as difference between origin and rotation origin
Vector2f rotationSize = getRotationSize();
float xOff = (mOrigin.x() - mRotationOrigin.x()) * rotationSize.x();
float yOff = (mOrigin.y() - mRotationOrigin.y()) * rotationSize.y();
// transform to offset point
// Transform to offset point
if (xOff != 0.0 || yOff != 0.0)
mTransform.translate(Vector3f(xOff * -1, yOff * -1, 0.0f));
// apply rotation transform
// Apply rotation transform
mTransform.rotateZ(mRotation);
// Tranform back to original point
if (xOff != 0.0 || yOff != 0.0)
mTransform.translate(Vector3f(xOff, yOff, 0.0f));
}
mTransform.translate(Vector3f(mOrigin.x() * mSize.x() * -1, mOrigin.y() * mSize.y() * -1, 0.0f));
mTransform.translate(Vector3f(mOrigin.x() * mSize.x() * -1,
mOrigin.y() * mSize.y() * -1, 0.0f));
return mTransform;
}
@ -296,12 +297,11 @@ std::string GuiComponent::getValue() const
void GuiComponent::textInput(const char* text)
{
for (auto iter = mChildren.cbegin(); iter != mChildren.cend(); iter++)
{
(*iter)->textInput(text);
}
}
void GuiComponent::setAnimation(Animation* anim, int delay, std::function<void()> finishedCallback, bool reverse, unsigned char slot)
void GuiComponent::setAnimation(Animation* anim, int delay,
std::function<void()> finishedCallback, bool reverse, unsigned char slot)
{
assert(slot < MAX_ANIMATIONS);
@ -315,12 +315,12 @@ void GuiComponent::setAnimation(Animation* anim, int delay, std::function<void()
bool GuiComponent::stopAnimation(unsigned char slot)
{
assert(slot < MAX_ANIMATIONS);
if(mAnimationMap[slot])
{
if (mAnimationMap[slot]) {
delete mAnimationMap[slot];
mAnimationMap[slot] = NULL;
return true;
}else{
}
else {
return false;
}
}
@ -328,13 +328,13 @@ bool GuiComponent::stopAnimation(unsigned char slot)
bool GuiComponent::cancelAnimation(unsigned char slot)
{
assert(slot < MAX_ANIMATIONS);
if(mAnimationMap[slot])
{
if (mAnimationMap[slot]) {
mAnimationMap[slot]->removeFinishedCallback();
delete mAnimationMap[slot];
mAnimationMap[slot] = NULL;
return true;
}else{
}
else {
return false;
}
}
@ -342,16 +342,17 @@ bool GuiComponent::cancelAnimation(unsigned char slot)
bool GuiComponent::finishAnimation(unsigned char slot)
{
assert(slot < MAX_ANIMATIONS);
if(mAnimationMap[slot])
{
// skip to animation's end
const bool done = mAnimationMap[slot]->update(mAnimationMap[slot]->getAnimation()->getDuration() - mAnimationMap[slot]->getTime());
if (mAnimationMap[slot]) {
// Skip to animation's end
const bool done = mAnimationMap[slot]->update(mAnimationMap[slot]->
getAnimation()->getDuration() - mAnimationMap[slot]->getTime());
assert(done);
delete mAnimationMap[slot]; // will also call finishedCallback
delete mAnimationMap[slot]; // Will also call finishedCallback
mAnimationMap[slot] = NULL;
return true;
}else{
}
else {
return false;
}
}
@ -360,16 +361,15 @@ bool GuiComponent::advanceAnimation(unsigned char slot, unsigned int time)
{
assert(slot < MAX_ANIMATIONS);
AnimationController* anim = mAnimationMap[slot];
if(anim)
{
if (anim) {
bool done = anim->update(time);
if(done)
{
if (done) {
mAnimationMap[slot] = NULL;
delete anim;
}
return true;
}else{
}
else {
return false;
}
}
@ -403,17 +403,18 @@ int GuiComponent::getAnimationTime(unsigned char slot) const
return mAnimationMap[slot]->getTime();
}
void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties)
{
Vector2f scale = getParent() ? getParent()->getSize() : Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
Vector2f scale = getParent() ? getParent()->getSize()
: Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "");
if (!elem)
return;
using namespace ThemeFlags;
if(properties & POSITION && elem->has("pos"))
{
if (properties & POSITION && elem->has("pos")) {
Vector2f denormalized = elem->get<Vector2f>("pos") * scale;
setPosition(Vector3f(denormalized.x(), denormalized.y(), 0));
}
@ -421,9 +422,11 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std
if (properties & ThemeFlags::SIZE && elem->has("size"))
setSize(elem->get<Vector2f>("size") * scale);
// position + size also implies origin
if((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) && elem->has("origin"))
// Position + size also implies origin
if ((properties & ORIGIN || (properties & POSITION &&
properties & ThemeFlags::SIZE)) && elem->has("origin")) {
setOrigin(elem->get<Vector2f>("origin"));
}
if (properties & ThemeFlags::ROTATION) {
if (elem->has("rotation"))
@ -445,8 +448,7 @@ void GuiComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std
void GuiComponent::updateHelpPrompts()
{
if(getParent())
{
if (getParent()) {
getParent()->updateHelpPrompts();
return;
}

View file

@ -1,3 +1,9 @@
//
// GuiComponent.h
//
// Basic GUI component handling such as placement, rotation, Z-order, rendering and animation.
//
#pragma once
#ifndef ES_CORE_GUI_COMPONENT_H
#define ES_CORE_GUI_COMPONENT_H
@ -26,35 +32,46 @@ public:
virtual void textInput(const char* text);
// Called when input is received.
//Return true if the input is consumed, false if it should continue to be passed to other children.
// Return true if the input is consumed, false if
// it should continue to be passed to other children.
virtual bool input(InputConfig* config, Input input);
//Called when time passes. Default implementation calls updateSelf(deltaTime) and updateChildren(deltaTime) - so you should probably call GuiComponent::update(deltaTime) at some point (or at least updateSelf so animations work).
// Called when time passes.
// Default implementation calls updateSelf(deltaTime) and updateChildren(deltaTime).
// So you should probably call GuiComponent::update(deltaTime) at some point (or at
// least updateSelf so animations work).
virtual void update(int deltaTime);
//Called when it's time to render. By default, just calls renderChildren(parentTrans * getTransform()).
// Called when it's time to render.
// By default, just calls renderChildren(parentTrans * getTransform()).
// You probably want to override this like so:
//1. Calculate the new transform that your control will draw at with Transform4x4f t = parentTrans * getTransform().
//2. Set the renderer to use that new transform as the model matrix - Renderer::setMatrix(t);
// 1. Calculate the new transform that your control will draw at with
// Transform4x4f t = parentTrans * getTransform().
// 2. Set the renderer to use that new transform as the model matrix
// Renderer::setMatrix(t);
// 3. Draw your component.
// 4. Tell your children to render, based on your component's transform - renderChildren(t).
virtual void render(const Transform4x4f& parentTrans);
Vector3f getPosition() const;
inline void setPosition(const Vector3f& offset) { setPosition(offset.x(), offset.y(), offset.z()); }
inline void setPosition(const Vector3f& offset)
{ setPosition(offset.x(), offset.y(), offset.z()); }
void setPosition(float x, float y, float z = 0.0f);
virtual void onPositionChanged() {};
//Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
// Sets the origin as a percentage of this image.
// (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
Vector2f getOrigin() const;
void setOrigin(float originX, float originY);
inline void setOrigin(Vector2f origin) { setOrigin(origin.x(), origin.y()); }
virtual void onOriginChanged() {};
//Sets the rotation origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
// Sets the rotation origin as a percentage of this image.
// (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
Vector2f getRotationOrigin() const;
void setRotationOrigin(float originX, float originY);
inline void setRotationOrigin(Vector2f origin) { setRotationOrigin(origin.x(), origin.y()); }
inline void setRotationOrigin(Vector2f origin)
{ setRotationOrigin(origin.x(), origin.y()); }
virtual Vector2f getSize() const;
inline void setSize(const Vector2f& size) { setSize(size.x(), size.y()); }
@ -92,15 +109,22 @@ public:
unsigned int getChildCount() const;
GuiComponent* getChild(unsigned int i) const;
// animation will be automatically deleted when it completes or is stopped.
// Animation will be automatically deleted when it completes or is stopped.
bool isAnimationPlaying(unsigned char slot) const;
bool isAnimationReversed(unsigned char slot) const;
int getAnimationTime(unsigned char slot) const;
void setAnimation(Animation* animation, int delay = 0, std::function<void()> finishedCallback = nullptr, bool reverse = false, unsigned char slot = 0);
void setAnimation(Animation* animation, int delay = 0,
std::function<void()> finishedCallback = nullptr,
bool reverse = false, unsigned char slot = 0);
bool stopAnimation(unsigned char slot);
bool cancelAnimation(unsigned char slot); // Like stopAnimation, but doesn't call finishedCallback - only removes the animation, leaving things in their current state. Returns true if successful (an animation was in this slot).
bool finishAnimation(unsigned char slot); // Calls update(1.f) and finishedCallback, then deletes the animation - basically skips to the end. Returns true if successful (an animation was in this slot).
bool advanceAnimation(unsigned char slot, unsigned int time); // Returns true if successful (an animation was in this slot).
// Like stopAnimation, but doesn't call finishedCallback - only removes the animation, leaving
// things in their current state. Returns true if successful (an animation was in this slot).
bool cancelAnimation(unsigned char slot);
// Calls update(1.f) and finishedCallback, then deletes the animation - basically skips
// to the end. Returns true if successful (an animation was in this slot).
bool finishAnimation(unsigned char slot);
// Returns true if successful (an animation was in this slot).
bool advanceAnimation(unsigned char slot, unsigned int time);
void stopAllAnimations();
void cancelAllAnimations();
@ -124,7 +148,8 @@ public:
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
// You probably want to keep this behavior for any derived classes as well as add your own.
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view, const std::string& element, unsigned int properties);
// Returns a list of help prompts.
virtual std::vector<HelpPrompt> getHelpPrompts() { return std::vector<HelpPrompt>(); };
@ -137,10 +162,12 @@ public:
// Returns true if the component is busy doing background processing (e.g. HTTP downloads)
bool isProcessing() const;
const static unsigned char MAX_ANIMATIONS = 4;
protected:
void renderChildren(const Transform4x4f& transform) const;
void updateSelf(int deltaTime); // updates animations
void updateChildren(int deltaTime); // updates animations
void updateSelf(int deltaTime); // Updates animations
void updateChildren(int deltaTime); // Updates animations
unsigned char mOpacity;
Window* mWindow;
@ -162,11 +189,9 @@ protected:
bool mIsProcessing;
bool mVisible;
public:
const static unsigned char MAX_ANIMATIONS = 4;
private:
Transform4x4f mTransform; //Don't access this directly! Use getTransform()!
// Don't access this directly! Use getTransform()!
Transform4x4f mTransform;
AnimationController* mAnimationMap[MAX_ANIMATIONS];
};

View file

@ -68,6 +68,7 @@ void Settings::setDefaults()
mStringMap["StartupSystem"] = "";
mBoolMap["DisableKidStartMenu"] = true;
mStringMap["MediaDirectory"] = "";
mStringMap["DefaultSortOrder"] = "";
mBoolMap["VSync"] = true;
@ -139,9 +140,9 @@ void Settings::setDefaults()
mStringMap["CollectionSystemsAuto"] = "";
mStringMap["CollectionSystemsCustom"] = "";
mBoolMap["CollectionShowSystemInfo"] = true;
mBoolMap["SortAllSystems"] = false;
mBoolMap["UseCustomCollectionsSystem"] = true;
mBoolMap["FavFirstCustom"] = true;
mBoolMap["FavoritesFirst"] = true;
mBoolMap["LaunchstringOverride"] = true;

View file

@ -1,19 +1,39 @@
//
// GuiComplexTextEditPopup.cpp
//
// Text edit popup with a title, two text strings, a text input box and buttons
// to load the second text string and to clear the input field.
// Intended for updating settings for configuration files and similar.
//
#include "guis/GuiComplexTextEditPopup.h"
#include "components/ButtonComponent.h"
#include "components/MenuComponent.h"
#include "components/TextEditComponent.h"
GuiComplexTextEditPopup::GuiComplexTextEditPopup(Window* window, const std::string& title, const std::string& infoString1, const std::string& infoString2,
const std::string& initValue, const std::function<void(const std::string&)>& okCallback, bool multiLine, const char* acceptBtnText)
: GuiComponent(window), mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 5)), mMultiLine(multiLine)
GuiComplexTextEditPopup::GuiComplexTextEditPopup(
Window* window,
const std::string& title,
const std::string& infoString1,
const std::string& infoString2,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine, const char* acceptBtnText)
: GuiComponent(window),
mBackground(window, ":/frame.png"),
mGrid(window, Vector2i(1, 5)),
mMultiLine(multiLine)
{
addChild(&mBackground);
addChild(&mGrid);
mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title), Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER);
mInfoString1 = std::make_shared<TextComponent>(mWindow, infoString1, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mInfoString2 = std::make_shared<TextComponent>(mWindow, infoString2, Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mTitle = std::make_shared<TextComponent>(mWindow, Utils::String::toUpper(title),
Font::get(FONT_SIZE_MEDIUM), 0x555555FF, ALIGN_CENTER);
mInfoString1 = std::make_shared<TextComponent>(mWindow, infoString1,
Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mInfoString2 = std::make_shared<TextComponent>(mWindow, infoString2,
Font::get(FONT_SIZE_SMALL), 0x555555FF, ALIGN_CENTER);
mText = std::make_shared<TextEditComponent>(mWindow);
mText->setValue(initValue);
@ -22,27 +42,36 @@ GuiComplexTextEditPopup::GuiComplexTextEditPopup(Window* window, const std::stri
mText->setCursor(initValue.size());
std::vector< std::shared_ptr<ButtonComponent> > buttons;
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, acceptBtnText, acceptBtnText, [this, okCallback] { okCallback(mText->getValue()); delete this; }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "LOAD", "load default string", [this, infoString2] { mText->setValue(infoString2); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear string", [this] { mText->setValue(""); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes", [this] { delete this; }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, acceptBtnText, acceptBtnText,
[this, okCallback] { okCallback(mText->getValue()); delete this; }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "LOAD", "load default string",
[this, infoString2] { mText->setValue(infoString2); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CLEAR", "clear string",
[this] { mText->setValue(""); }));
buttons.push_back(std::make_shared<ButtonComponent>(mWindow, "CANCEL", "discard changes",
[this] { delete this; }));
mButtonGrid = makeButtonGrid(mWindow, buttons);
mGrid.setEntry(mTitle, Vector2i(0, 0), false, true);
mGrid.setEntry(mInfoString1, Vector2i(0, 1), false, true);
mGrid.setEntry(mInfoString2, Vector2i(0, 2), false, true);
mGrid.setEntry(mText, Vector2i(0, 3), true, false, Vector2i(1, 1), GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
mGrid.setEntry(mText, Vector2i(0, 3), true, false, Vector2i(1, 1),
GridFlags::BORDER_TOP | GridFlags::BORDER_BOTTOM);
mGrid.setEntry(mButtonGrid, Vector2i(0, 4), true, false);
mGrid.setRowHeightPerc(1, 0.15, true);
float textHeight = mText->getFont()->getHeight();
if (multiLine)
textHeight *= 6;
mText->setSize(0, textHeight);
setSize(Renderer::getScreenWidth() * 0.75f, mTitle->getFont()->getHeight() + textHeight + mButtonGrid->getSize().y() + 220);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, (Renderer::getScreenHeight() - mSize.y()) / 2);
setSize(Renderer::getScreenWidth() * 0.75f,mTitle->getFont()->getHeight() +
textHeight + mButtonGrid->getSize().y() + 220);
setPosition((Renderer::getScreenWidth() - mSize.x()) / 2,
(Renderer::getScreenHeight() - mSize.y()) / 2);
}
void GuiComplexTextEditPopup::onSizeChanged()
@ -51,7 +80,7 @@ void GuiComplexTextEditPopup::onSizeChanged()
mText->setSize(mSize.x() - 40, mText->getSize().y());
// update grid
// Update grid
mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y());
mGrid.setRowHeightPerc(2, mButtonGrid->getSize().y() / mSize.y());
mGrid.setSize(mSize);
@ -62,9 +91,8 @@ bool GuiComplexTextEditPopup::input(InputConfig* config, Input input)
if (GuiComponent::input(config, input))
return true;
// pressing back when not text editing closes us
if(config->isMappedTo("b", input) && input.value)
{
// Pressing back button when not text editing closes us
if (config->isMappedTo("b", input) && input.value) {
delete this;
return true;
}

View file

@ -1,3 +1,11 @@
//
// GuiComplexTextEditPopup.h
//
// Text edit popup with a title, two text strings, a text input box and buttons
// to load the second text string and to clear the input field.
// Intended for updating settings for configuration files and similar.
//
#pragma once
#ifndef ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H
#define ES_CORE_GUIS_GUI_COMPLEX_TEXT_EDIT_POPUP_H
@ -12,8 +20,15 @@ class TextEditComponent;
class GuiComplexTextEditPopup : public GuiComponent
{
public:
GuiComplexTextEditPopup(Window* window, const std::string& title, const std::string& infoString1, const std::string& infoString2,
const std::string& initValue, const std::function<void(const std::string&)>& okCallback, bool multiLine, const char* acceptBtnText = "OK");
GuiComplexTextEditPopup(
Window* window,
const std::string& title,
const std::string& infoString1,
const std::string& infoString2,
const std::string& initValue,
const std::function<void(const std::string&)>& okCallback,
bool multiLine,
const char* acceptBtnText = "OK");
bool input(InputConfig* config, Input input);
void onSizeChanged();