From d0cdbf215958e01ffea585a1c6fd75c03b306c46 Mon Sep 17 00:00:00 2001 From: pjft Date: Mon, 12 Jun 2017 17:38:59 +0100 Subject: [PATCH] Adding support for Virtual Systems - Refactoring System Environment data - Added Virtual System Manager class - Added "all", "favorites" and "last played" systems - Added GuiInfoPopup class for notifications - Added Favorites to metadata, as well as a shortcut to toggle favorites - Added warning if enabling systems but themes don't support it - Added "filter by favorites" per system - Adjusted "Go to Random Game" behavior to account for the fact that we now have an "All Games" system - Added "sort by system name" for the collections --- es-app/CMakeLists.txt | 6 + es-app/src/CollectionSystemManager.cpp | 441 ++++++++++++++++++ es-app/src/CollectionSystemManager.h | 74 +++ es-app/src/FileData.cpp | 167 +++++-- es-app/src/FileData.h | 43 +- es-app/src/FileFilterIndex.cpp | 48 +- es-app/src/FileFilterIndex.h | 11 +- es-app/src/FileSorts.cpp | 29 +- es-app/src/FileSorts.h | 4 +- es-app/src/Gamelist.cpp | 4 +- es-app/src/MetaData.cpp | 59 +-- es-app/src/MetaData.h | 1 + es-app/src/SystemData.cpp | 195 ++++---- es-app/src/SystemData.h | 38 +- es-app/src/SystemScreenSaver.cpp | 33 +- .../src/guis/GuiCollectionSystemsOptions.cpp | 105 +++++ es-app/src/guis/GuiCollectionSystemsOptions.h | 31 ++ es-app/src/guis/GuiFastSelect.cpp | 2 +- es-app/src/guis/GuiGamelistOptions.cpp | 9 +- es-app/src/guis/GuiInfoPopup.cpp | 114 +++++ es-app/src/guis/GuiInfoPopup.h | 27 ++ es-app/src/guis/GuiMenu.cpp | 17 +- es-app/src/guis/GuiMenu.h | 1 + es-app/src/guis/GuiMetaDataEd.cpp | 11 +- es-app/src/main.cpp | 1 + es-app/src/views/SystemView.cpp | 7 +- es-app/src/views/ViewController.cpp | 13 +- .../src/views/gamelist/BasicGameListView.cpp | 32 +- es-app/src/views/gamelist/BasicGameListView.h | 3 +- es-app/src/views/gamelist/IGameListView.h | 2 +- .../views/gamelist/ISimpleGameListView.cpp | 25 +- es-core/src/Settings.cpp | 1 + es-core/src/Util.cpp | 101 ++++ es-core/src/Util.h | 13 + es-core/src/Window.cpp | 13 +- es-core/src/Window.h | 10 + es-core/src/components/MenuComponent.cpp | 2 +- es-core/src/components/SwitchComponent.cpp | 17 + es-core/src/components/SwitchComponent.h | 2 + es-core/src/resources/Font.h | 1 + 40 files changed, 1428 insertions(+), 285 deletions(-) create mode 100644 es-app/src/CollectionSystemManager.cpp create mode 100644 es-app/src/CollectionSystemManager.h create mode 100644 es-app/src/guis/GuiCollectionSystemsOptions.cpp create mode 100644 es-app/src/guis/GuiCollectionSystemsOptions.h create mode 100644 es-app/src/guis/GuiInfoPopup.cpp create mode 100644 es-app/src/guis/GuiInfoPopup.h diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt index d44c16ac1..3cff331c2 100644 --- a/es-app/CMakeLists.txt +++ b/es-app/CMakeLists.txt @@ -12,6 +12,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.h ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreenSaver.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemManager.h # GuiComponents ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h @@ -30,6 +31,8 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.h # Scrapers ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.h @@ -63,6 +66,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/Gamelist.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SystemScreenSaver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemManager.cpp # GuiComponents ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp @@ -80,6 +84,8 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperMulti.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiScraperStart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiGamelistFilter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiCollectionSystemsOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiInfoPopup.cpp # Scrapers ${CMAKE_CURRENT_SOURCE_DIR}/src/scrapers/Scraper.cpp diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp new file mode 100644 index 000000000..eb8761ecb --- /dev/null +++ b/es-app/src/CollectionSystemManager.cpp @@ -0,0 +1,441 @@ +#include "SystemData.h" +#include "Gamelist.h" +#include +#include "Util.h" +#include +#include +#include +#include +#include "Renderer.h" +#include "Log.h" +#include "InputManager.h" +#include +#include "Settings.h" +#include "FileSorts.h" +#include "pugixml/src/pugixml.hpp" +#include "guis/GuiInfoPopup.h" + +namespace fs = boost::filesystem; + +CollectionSystemManager* CollectionSystemManager::sInstance = NULL; + +CollectionSystemManager* CollectionSystemManager::get() +{ + assert(sInstance); + return sInstance; +} + +void CollectionSystemManager::init(Window* window) +{ + assert(!sInstance); + sInstance = new CollectionSystemManager(window); +} + +CollectionSystemManager::CollectionSystemManager(Window* window) : mWindow(window) +{ + CollectionSystemDecl systemDecls[] = { + //type name long name //default sort // theme folder // isCustom + { AUTO_ALL_GAMES, "all", "all games", "filename, ascending", "auto-allgames", false }, + { AUTO_LAST_PLAYED, "recent", "last played", "last played, descending", "auto-lastplayed", false }, + { AUTO_FAVORITES, "favorites", "favorites", "filename, ascending", "auto-favorites", false }, + { CUSTOM_COLLECTION, "custom", "custom", "filename, ascending", "custom-collections", true } + }; + + // create a map + std::vector tempSystemDecl = std::vector(systemDecls, systemDecls + sizeof(systemDecls) / sizeof(systemDecls[0])); + + for (std::vector::iterator it = tempSystemDecl.begin(); it != tempSystemDecl.end(); ++it ) + { + mCollectionSystemDecls[(*it).name] = (*it); + //mCollectionSystemDecls.insert(std::make_pair((*it).name,(*it))); + } + + // creating standard environment data + mCollectionEnvData = new SystemEnvironmentData; + mCollectionEnvData->mStartPath = ""; + std::vector exts; + mCollectionEnvData->mSearchExtensions = exts; + mCollectionEnvData->mLaunchCommand = ""; + std::vector allPlatformIds; + allPlatformIds.push_back(PlatformIds::PLATFORM_IGNORE); + mCollectionEnvData->mPlatformIds = allPlatformIds; + + // TO DO: Create custom editing help style + +} + +CollectionSystemManager::~CollectionSystemManager() +{ + assert(sInstance == this); + sInstance = NULL; +} + +void CollectionSystemManager::loadEnabledListFromSettings() +{ + // we parse the settings + std::vector selected = commaStringToVector(Settings::getInstance()->getString("CollectionSystemsAuto")); + + // iterate the map + for(std::map::iterator it = mAllCollectionSystems.begin() ; it != mAllCollectionSystems.end() ; it++ ) + { + it->second.isEnabled = (std::find(selected.begin(), selected.end(), it->first) != selected.end()); + } +} + +void CollectionSystemManager::initAvailableSystemsList() +{ + +} + +std::vector CollectionSystemManager::getSystemsFromConfig() +{ + std::vector systems; + std::string path = SystemData::getConfigPath(false); + + if(!fs::exists(path)) + { + return systems; + } + + pugi::xml_document doc; + pugi::xml_parse_result res = doc.load_file(path.c_str()); + + if(!res) + { + return systems; + } + + //actually read the file + pugi::xml_node systemList = doc.child("systemList"); + + if(!systemList) + { + return systems; + } + + for(pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system")) + { + // theme folder + std::string themeFolder = system.child("theme").text().get(); + systems.push_back(themeFolder); + } + std::sort(systems.begin(), systems.end()); + return systems; +} + +std::vector CollectionSystemManager::getSystemsFromTheme() +{ + std::vector systems; + auto themeSets = ThemeData::getThemeSets(); + if(themeSets.empty()) + { + // no theme sets available + return systems; + } + + auto set = themeSets.find(Settings::getInstance()->getString("ThemeSet")); + if(set == themeSets.end()) + { + // currently selected theme set is missing, so just pick the first available set + set = themeSets.begin(); + Settings::getInstance()->setString("ThemeSet", set->first); + } + + boost::filesystem::path themePath = set->second.path; + + if (fs::exists(themePath)) + { + fs::directory_iterator end_itr; // default construction yields past-the-end + for (fs::directory_iterator itr(themePath); itr != end_itr; ++itr) + { + if (fs::is_directory(itr->status())) + { + //... here you have a directory + std::string folder = itr->path().string(); + folder = folder.substr(themePath.string().size()+1); + + if(fs::exists(set->second.getThemePath(folder))) + { + systems.push_back(folder); + } + } + } + } + std::sort(systems.begin(), systems.end()); + return systems; +} + +std::vector CollectionSystemManager::getAutoThemeFolders() +{ + std::vector systems; + for(std::map::iterator it = mCollectionSystemDecls.begin() ; it != mCollectionSystemDecls.end() ; it++ ) + { + CollectionSystemDecl sysDecl = it->second; + if (!sysDecl.isCustom) + { + systems.push_back(sysDecl.themeFolder); + } + } + return systems; +} + +bool CollectionSystemManager::isThemeAutoCompatible() +{ + std::vector cfgSys = getAutoThemeFolders(); + for(auto sysIt = cfgSys.begin(); sysIt != cfgSys.end(); sysIt++) + { + if(!themeFolderExists(*sysIt)) + return false; + } + return true; +} + +bool CollectionSystemManager::themeFolderExists(std::string folder) +{ + std::vector themeSys = getSystemsFromTheme(); + return std::find(themeSys.begin(), themeSys.end(), folder) != themeSys.end(); +} + +std::vector CollectionSystemManager::getUnusedSystemsFromTheme() +{ + std::vector cfgSys = getSystemsFromConfig(); + std::vector themeSys = getSystemsFromTheme(); + for(auto sysIt = themeSys.begin(); sysIt != themeSys.end(); ) + { + if (std::find(cfgSys.begin(), cfgSys.end(), *sysIt) != cfgSys.end()) + { + sysIt = themeSys.erase(sysIt); + } + else + { + sysIt++; + } + } + return themeSys; +} + +FileData::SortType CollectionSystemManager::getSortType(std::string desc) { + std::vector SortTypes = FileSorts::SortTypes; + // 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 + return FileSorts::SortTypes.at(0); +} + +void CollectionSystemManager::loadCollectionSystems() +{ + loadAutoCollectionSystems(); + // we will also load custom systems here in the future + loadCustomCollectionSystems(); + // Now see which ones are enabled + loadEnabledListFromSettings(); + // add to the main System Vector, and create Views as needed + updateSystemsList(); +} + +void CollectionSystemManager::updateSystemsList() +{ + // remove all Collection Systems + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); ) + { + if ((*sysIt)->isCollection()) + { + sysIt = SystemData::sSystemVector.erase(sysIt); + } + else + { + sysIt++; + } + } + + // add enabled ones + for(std::map::iterator it = mAllCollectionSystems.begin() ; it != mAllCollectionSystems.end() ; it++ ) + { + if(it->second.isEnabled) + { + SystemData::sSystemVector.push_back(it->second.system); + } + } + + // remove disabled gamelist views + // create gamelist views if needed + // iterate the map +} + +void CollectionSystemManager::loadAutoCollectionSystems() +{ + for(std::map::iterator it = mCollectionSystemDecls.begin() ; it != mCollectionSystemDecls.end() ; it++ ) + { + CollectionSystemDecl sysDecl = it->second; + if (!sysDecl.isCustom && !findCollectionSystem(sysDecl.name)) + { + SystemData* newSys = new SystemData(sysDecl.name, sysDecl.longName, mCollectionEnvData, sysDecl.themeFolder, true); + + FileData* rootFolder = newSys->getRootFolder(); + FileFilterIndex* index = newSys->getIndex(); + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + if ((*sysIt)->isGameSystem()) { + std::vector files = (*sysIt)->getRootFolder()->getFilesRecursive(GAME); + for(auto gameIt = files.begin(); gameIt != files.end(); gameIt++) + { + bool include = includeFileInAutoCollections((*gameIt)); + switch(sysDecl.type) { + case AUTO_LAST_PLAYED: + include = include && (*gameIt)->metadata.get("playcount") > "0"; + break; + case AUTO_FAVORITES: + // we may still want to add files we don't want in auto collections in "favorites" + include = (*gameIt)->metadata.get("favorite") == "true"; + break; + } + + if (include) { + CollectionFileData* newGame = new CollectionFileData(*gameIt, newSys); + rootFolder->addChild(newGame); + index->addToIndex(newGame); + } + } + } + } + rootFolder->sort(getSortType(sysDecl.defaultSort)); + mAutoCollectionSystems.push_back(newSys); + + CollectionSystemData newCollectionData; + newCollectionData.system = newSys; + newCollectionData.decl = sysDecl; + newCollectionData.isEnabled = false; + mAllCollectionSystems[sysDecl.name] = newCollectionData; + } + } +} + +void CollectionSystemManager::loadCustomCollectionSystems() +{ + // Load custom systems into memory + // Check unassigned theme folders + // Check settings string for selected/enabled systems, if there are any that aren't in the theme + // Check saved preferences for each of those and load them if we can + // Load settings to see which systems are Enabled +} + +SystemData* CollectionSystemManager::findCollectionSystem(std::string name) +{ + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + if ((*sysIt)->getName() == name) { + // found it! + return (*sysIt); + } + } + return NULL; +} + +// this updates all collection files related to the argument file +void CollectionSystemManager::updateCollectionSystems(FileData* file) +{ + // collection files use the full path as key, to avoid clashes + std::string key = file->getFullPath(); + // find games in collection systems + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + if ((*sysIt)->isCollection()) { + const std::unordered_map& children = (*sysIt)->getRootFolder()->getChildrenByFilename(); + bool found = children.find(key) != children.end(); + FileData* rootFolder = (*sysIt)->getRootFolder(); + FileFilterIndex* fileIndex = (*sysIt)->getIndex(); + std::string name = (*sysIt)->getName(); + if (found) { + // if we found it, we need to update it + FileData* collectionEntry = children.at(key); + // remove from index, so we can re-index metadata after refreshing + fileIndex->removeFromIndex(collectionEntry); + collectionEntry->refreshMetadata(); + if (name == "favorites" && file->metadata.get("favorite") == "false") { + // need to check if still marked as favorite, if not remove + ViewController::get()->getGameListView((*sysIt)).get()->remove(collectionEntry, false); + ViewController::get()->onFileChanged((*sysIt)->getRootFolder(), FILE_REMOVED); + } + else + { + // re-index with new metadata + fileIndex->addToIndex(collectionEntry); + ViewController::get()->onFileChanged(collectionEntry, FILE_METADATA_CHANGED); + } + } + else + { + // we didn't find it here - we need to check if we should add it + if (name == "recent" && file->metadata.get("playcount") > "0" || + name == "favorites" && file->metadata.get("favorite") == "true") { + CollectionFileData* newGame = new CollectionFileData(file, (*sysIt)); + rootFolder->addChild(newGame); + fileIndex->addToIndex(newGame); + ViewController::get()->onFileChanged(file, FILE_METADATA_CHANGED); + ViewController::get()->getGameListView((*sysIt))->onFileChanged(newGame, FILE_METADATA_CHANGED); + } + } + rootFolder->sort(getSortType(mCollectionSystemDecls[name].defaultSort)); + ViewController::get()->onFileChanged(rootFolder, FILE_SORTED); + } + } +} + +// this deletes collection files from collection systems +void CollectionSystemManager::deleteCollectionFiles(FileData* file) +{ + // collection files use the full path as key, to avoid clashes + std::string key = file->getFullPath(); + // find games in collection systems + for(auto sysIt = SystemData::sSystemVector.begin(); sysIt != SystemData::sSystemVector.end(); sysIt++) + { + if ((*sysIt)->isCollection()) { + const std::unordered_map& children = (*sysIt)->getRootFolder()->getChildrenByFilename(); + + bool found = children.find(key) != children.end(); + if (found) { + FileData* collectionEntry = children.at(key); + ViewController::get()->getGameListView((*sysIt)).get()->remove(collectionEntry, false); + } + } + } +} + +bool CollectionSystemManager::toggleGameInCollection(FileData* file, std::string collection) +{ + if (file->getType() == GAME) + { + GuiInfoPopup* s; + + MetaDataList* md = &file->getSourceFileData()->metadata; + std::string value = md->get("favorite"); + if (value == "false") + { + md->set("favorite", "true"); + s = new GuiInfoPopup(mWindow, "Added '" + removeParenthesis(file->getName()) + "' to 'Favorites'", 4000); + }else + { + md->set("favorite", "false"); + s = new GuiInfoPopup(mWindow, "Removed '" + removeParenthesis(file->getName()) + "' from 'Favorites'", 4000); + } + mWindow->setInfoPopup(s); + updateCollectionSystems(file->getSourceFileData()); + return true; + } + return false; +} + +bool CollectionSystemManager::includeFileInAutoCollections(FileData* file) +{ + // we exclude non-game files from collections (i.e. "kodi", 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 + return file->getName() != "kodi"; +} \ No newline at end of file diff --git a/es-app/src/CollectionSystemManager.h b/es-app/src/CollectionSystemManager.h new file mode 100644 index 000000000..9482786d2 --- /dev/null +++ b/es-app/src/CollectionSystemManager.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include "FileData.h" +#include "Window.h" +#include "MetaData.h" +#include "PlatformId.h" +#include "ThemeData.h" +#include "FileFilterIndex.h" +#include "SystemData.h" +#include "views/ViewController.h" + +enum CollectionSystemType +{ + AUTO_ALL_GAMES, + AUTO_LAST_PLAYED, + AUTO_FAVORITES, + CUSTOM_COLLECTION +}; + +struct CollectionSystemDecl +{ + CollectionSystemType type; // type of system + std::string name; + std::string longName; + std::string defaultSort; + std::string themeFolder; + bool isCustom; +}; + +struct CollectionSystemData +{ + SystemData* system; + CollectionSystemDecl decl; + bool isEnabled; +}; + +class CollectionSystemManager +{ +public: + CollectionSystemManager(Window* window); + ~CollectionSystemManager(); + static void init(Window* window); + static CollectionSystemManager* get(); + void loadEnabledListFromSettings(); + void loadCollectionSystems(); + void updateCollectionSystems(FileData* file); + void deleteCollectionFiles(FileData* file); + inline std::map getCollectionSystems() { return mAllCollectionSystems; }; + void updateSystemsList(); + bool isThemeAutoCompatible(); + bool toggleGameInCollection(FileData* file, std::string collection); + +private: + static CollectionSystemManager* sInstance; + std::map mCollectionSystemDecls; + SystemEnvironmentData* mCollectionEnvData; + static FileData::SortType getSortType(std::string desc); + void initAvailableSystemsList(); + std::vector getSystemsFromConfig(); + std::vector getSystemsFromTheme(); + std::vector getUnusedSystemsFromTheme(); + std::vector getAutoThemeFolders(); + bool themeFolderExists(std::string folder); + void loadAutoCollectionSystems(); + void loadCustomCollectionSystems(); // TO DO NEXT + SystemData* findCollectionSystem(std::string name); + bool includeFileInAutoCollections(FileData* file); + std::map mAllCollectionSystems; + std::vector mAutoCollectionSystems; + std::vector mCustomCollectionSystems; + Window* mWindow; +}; diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 54e6f3856..8352b6c52 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -1,54 +1,21 @@ #include "FileData.h" +#include "FileSorts.h" +#include "views/ViewController.h" #include "SystemData.h" +#include "Log.h" +#include "AudioManager.h" +#include "VolumeControl.h" +#include "Util.h" namespace fs = boost::filesystem; -std::string removeParenthesis(const std::string& str) -{ - // remove anything in parenthesis or brackets - // should be roughly equivalent to the regex replace "\((.*)\)|\[(.*)\]" with "" - // I would love to just use regex, but it's not worth pulling in another boost lib for one function that is used once - - std::string ret = str; - size_t start, end; - - static const int NUM_TO_REPLACE = 2; - static const char toReplace[NUM_TO_REPLACE*2] = { '(', ')', '[', ']' }; - - bool done = false; - while(!done) - { - done = true; - for(int i = 0; i < NUM_TO_REPLACE; i++) - { - end = ret.find_first_of(toReplace[i*2+1]); - start = ret.find_last_of(toReplace[i*2], end); - - if(start != std::string::npos && end != std::string::npos) - { - ret.erase(start, end - start + 1); - done = false; - } - } - } - - // also strip whitespace - end = ret.find_last_not_of(' '); - if(end != std::string::npos) - end++; - - ret = ret.substr(0, end); - - return ret; -} - - -FileData::FileData(FileType type, const fs::path& path, SystemData* system) - : mType(type), mPath(path), mSystem(system), mParent(NULL), metadata(type == GAME ? GAME_METADATA : FOLDER_METADATA) // metadata is REALLY set in the constructor! +FileData::FileData(FileType type, const fs::path& 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! { // 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(); } FileData::~FileData() @@ -56,7 +23,9 @@ FileData::~FileData() if(mParent) mParent->removeChild(this); - mChildren.clear(); + mSystem->getIndex()->removeFromIndex(this); + + mChildren.clear(); } std::string FileData::getDisplayName() const @@ -81,6 +50,11 @@ const std::string& FileData::getThumbnailPath() const return metadata.get("image"); } +const std::string& FileData::getName() +{ + return metadata.get("name"); +} + const std::vector& FileData::getChildrenListToDisplay() { FileFilterIndex* idx = mSystem->getIndex(); @@ -134,12 +108,21 @@ std::vector FileData::getFilesRecursive(unsigned int typeMask, bool d return out; } +std::string FileData::getKey() { + return getFileName(); +} + +FileData* FileData::getSourceFileData() +{ + return this; +} + void FileData::addChild(FileData* file) { assert(mType == FOLDER); assert(file->getParent() == NULL); - const std::string key = file->getPath().filename().string(); + const std::string key = file->getKey(); if (mChildrenByFilename.find(key) == mChildrenByFilename.end()) { mChildrenByFilename[key] = file; @@ -152,7 +135,7 @@ void FileData::removeChild(FileData* file) { assert(mType == FOLDER); assert(file->getParent() == this); - mChildrenByFilename.erase(file->getPath().filename().string()); + mChildrenByFilename.erase(file->getKey()); for(auto it = mChildren.begin(); it != mChildren.end(); it++) { if(*it == file) @@ -169,7 +152,7 @@ void FileData::removeChild(FileData* file) void FileData::sort(ComparisonFunction& comparator, bool ascending) { - std::sort(mChildren.begin(), mChildren.end(), comparator); + std::stable_sort(mChildren.begin(), mChildren.end(), comparator); for(auto it = mChildren.begin(); it != mChildren.end(); it++) { @@ -185,3 +168,95 @@ void FileData::sort(const SortType& type) { sort(*type.comparisonFunction, type.ascending); } + +void FileData::launchGame(Window* window) +{ + LOG(LogInfo) << "Attempting to launch game..."; + + AudioManager::getInstance()->deinit(); + VolumeControl::getInstance()->deinit(); + window->deinit(); + + std::string command = mEnvData->mLaunchCommand; + + const std::string rom = escapePath(getPath()); + const std::string basename = getPath().stem().string(); + const std::string rom_raw = fs::path(getPath()).make_preferred().string(); + + command = strreplace(command, "%ROM%", rom); + command = strreplace(command, "%BASENAME%", basename); + command = strreplace(command, "%ROM_RAW%", rom_raw); + + LOG(LogInfo) << " " << command; + int exitCode = runSystemCommand(command); + + if(exitCode != 0) + { + LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!"; + } + + window->init(); + VolumeControl::getInstance()->init(); + AudioManager::getInstance()->init(); + window->normalizeNextUpdate(); + + //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(timesPlayed))); + + //update last played time + boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time(); + gameToUpdate->metadata.setTime("lastplayed", time); + CollectionSystemManager::get()->updateCollectionSystems(gameToUpdate); +} + +CollectionFileData::CollectionFileData(FileData* file, SystemData* system) + : FileData(file->getType(), file->getPath(), file->getSystemEnvData(), system)/*, + mSourceFileData(file->getSourceFileData()), + mParent(NULL), + metadata(file->getSourceFileData()->metadata)*/ +{ + // we use this constructor to create a clone of the filedata, and change its system + mSourceFileData = file->getSourceFileData(); + refreshMetadata(); + mParent = NULL; + metadata = mSourceFileData->metadata; + mSystemName = mSourceFileData->getSystem()->getName(); +} + +CollectionFileData::~CollectionFileData() +{ + // need to remove collection file data at the collection object destructor + if(mParent) + mParent->removeChild(this); + mParent = NULL; +} + +std::string CollectionFileData::getKey() { + return getFullPath(); +} + +FileData* CollectionFileData::getSourceFileData() +{ + return mSourceFileData; +} + +void CollectionFileData::refreshMetadata() +{ + metadata = mSourceFileData->metadata; + mDirty = true; +} + +const std::string& CollectionFileData::getName() +{ + if (mDirty) { + mCollectionFileName = removeParenthesis(mSourceFileData->metadata.get("name")); + boost::trim(mCollectionFileName); + mCollectionFileName += " [" + strToUpper(mSourceFileData->getSystem()->getName()) + "]"; + mDirty = false; + } + return mCollectionFileName; +} \ No newline at end of file diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index fbc53738e..425f7f981 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -7,6 +7,7 @@ #include "MetaData.h" class SystemData; +struct SystemEnvironmentData; enum FileType { @@ -27,24 +28,21 @@ enum FileChangeType const char* fileTypeToString(FileType type); FileType stringToFileType(const char* str); -// Remove (.*) and [.*] from str -std::string removeParenthesis(const std::string& str); - // A tree node that holds information for a file. class FileData { public: - FileData(FileType type, const boost::filesystem::path& path, SystemData* system); + FileData(FileType type, const boost::filesystem::path& path, SystemEnvironmentData* envData, SystemData* system); virtual ~FileData(); - inline const std::string& getName() const { return metadata.get("name"); } + virtual const std::string& getName(); inline FileType getType() const { return mType; } inline const boost::filesystem::path& getPath() const { return mPath; } inline FileData* getParent() const { return mParent; } inline const std::unordered_map& getChildrenByFilename() const { return mChildrenByFilename; } inline const std::vector& getChildren() const { return mChildren; } inline SystemData* getSystem() const { return mSystem; } - + inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } virtual const std::string& getThumbnailPath() const; virtual const std::string& getVideoPath() const; virtual const std::string& getMarqueePath() const; @@ -57,12 +55,22 @@ public: inline bool isPlaceHolder() { return mType == PLACEHOLDER; }; + virtual inline void refreshMetadata() { return; }; + + virtual std::string getKey(); + inline std::string getFullPath() { return getPath().string(); }; + inline std::string getFileName() { return getPath().filename().string(); }; + 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) std::string getDisplayName() const; // 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 { @@ -76,15 +84,34 @@ public: void sort(ComparisonFunction& comparator, bool ascending = true); void sort(const SortType& type); - MetaDataList metadata; +protected: + FileData* mSourceFileData; + FileData* mParent; + std::string mSystemName; + private: FileType mType; boost::filesystem::path mPath; + SystemEnvironmentData* mEnvData; SystemData* mSystem; - FileData* mParent; std::unordered_map mChildrenByFilename; std::vector mChildren; std::vector mFilteredChildren; }; + +class CollectionFileData : public FileData +{ +public: + CollectionFileData(FileData* file, SystemData* system); + ~CollectionFileData(); + const std::string& getName(); + void refreshMetadata(); + FileData* getSourceFileData(); + std::string getKey(); +private: + // needs to be updated when metadata changes + std::string mCollectionFileName; + bool mDirty; +}; \ No newline at end of file diff --git a/es-app/src/FileFilterIndex.cpp b/es-app/src/FileFilterIndex.cpp index 5fe6a94c3..3f60539d8 100644 --- a/es-app/src/FileFilterIndex.cpp +++ b/es-app/src/FileFilterIndex.cpp @@ -4,10 +4,11 @@ #define INCLUDE_UNKNOWN false; FileFilterIndex::FileFilterIndex() - : filterByGenre(false), filterByPlayers(false), filterByPubDev(false), filterByRatings(false) + : filterByGenre(false), filterByPlayers(false), filterByPubDev(false), filterByRatings(false), filterByFavorites(false) { FilterDataDecl filterDecls[] = { //type //allKeys //filteredBy //filteredKeys //primaryKey //hasSecondaryKey //secondaryKey //menuLabel + { FAVORITES_FILTER, &favoritesIndexAllKeys, &filterByFavorites, &favoritesIndexFilteredKeys,"favorite", false, "", "FAVORITES" }, { GENRE_FILTER, &genreIndexAllKeys, &filterByGenre, &genreIndexFilteredKeys, "genre", true, "genre", "GENRE" }, { PLAYER_FILTER, &playersIndexAllKeys, &filterByPlayers, &playersIndexFilteredKeys, "players", false, "", "PLAYERS" }, { PUBDEV_FILTER, &pubDevIndexAllKeys, &filterByPubDev, &pubDevIndexFilteredKeys, "developer", true, "publisher", "PUBLISHER / DEVELOPER" }, @@ -23,7 +24,7 @@ FileFilterIndex::~FileFilterIndex() clearIndex(playersIndexAllKeys); clearIndex(pubDevIndexAllKeys); clearIndex(ratingsIndexAllKeys); - + clearIndex(favoritesIndexAllKeys); } std::vector& FileFilterIndex::getFilterDataDecls() @@ -96,6 +97,13 @@ std::string FileFilterIndex::getIndexableKey(FileData* game, FilterIndexType typ } break; } + case FAVORITES_FILTER: + { + if (game->getType() != GAME) + return "FALSE"; + key = strToUpper(game->metadata.get("favorite")); + break; + } } boost::trim(key); if (key.empty() || (type == RATINGS_FILTER && key == "0 STARS")) { @@ -110,6 +118,7 @@ void FileFilterIndex::addToIndex(FileData* game) managePlayerEntryInIndex(game); managePubDevEntryInIndex(game); manageRatingsEntryInIndex(game); + manageFavoritesEntryInIndex(game); } void FileFilterIndex::removeFromIndex(FileData* game) @@ -118,6 +127,7 @@ void FileFilterIndex::removeFromIndex(FileData* game) managePlayerEntryInIndex(game, true); managePubDevEntryInIndex(game, true); manageRatingsEntryInIndex(game, true); + manageFavoritesEntryInIndex(game, true); } void FileFilterIndex::setFilter(FilterIndexType type, std::vector* values) @@ -160,18 +170,21 @@ void FileFilterIndex::clearAllFilters() void FileFilterIndex::debugPrintIndexes() { - LOG(LogInfo) << "Printing Indexes..."; + LOG(LogError) << "Printing Indexes..."; for (auto x: playersIndexAllKeys) { - LOG(LogInfo) << "Multiplayer Index: " << x.first << ": " << x.second; + LOG(LogError) << "Multiplayer Index: " << x.first << ": " << x.second; } for (auto x: genreIndexAllKeys) { - LOG(LogInfo) << "Genre Index: " << x.first << ": " << x.second; + LOG(LogError) << "Genre Index: " << x.first << ": " << x.second; } for (auto x: ratingsIndexAllKeys) { - LOG(LogInfo) << "Ratings Index: " << x.first << ": " << x.second; + LOG(LogError) << "Ratings Index: " << x.first << ": " << x.second; } for (auto x: pubDevIndexAllKeys) { - LOG(LogInfo) << "PubDev Index: " << x.first << ": " << x.second; + LOG(LogError) << "PubDev Index: " << x.first << ": " << x.second; + } + for (auto x: favoritesIndexAllKeys) { + LOG(LogError) << "Favorites Index: " << x.first << ": " << x.second; } } @@ -232,10 +245,10 @@ bool FileFilterIndex::showFile(FileData* game) bool FileFilterIndex::isKeyBeingFilteredBy(std::string key, FilterIndexType type) { - const FilterIndexType filterTypes[4] = { PLAYER_FILTER, RATINGS_FILTER, GENRE_FILTER, PUBDEV_FILTER }; - std::vector filterKeysList[4] = { playersIndexFilteredKeys, ratingsIndexFilteredKeys, genreIndexFilteredKeys, pubDevIndexFilteredKeys }; + const FilterIndexType filterTypes[5] = { FAVORITES_FILTER, PLAYER_FILTER, RATINGS_FILTER, GENRE_FILTER, PUBDEV_FILTER }; + std::vector filterKeysList[5] = { favoritesIndexFilteredKeys, playersIndexFilteredKeys, ratingsIndexFilteredKeys, genreIndexFilteredKeys, pubDevIndexFilteredKeys }; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 5; i++) { if (filterTypes[i] == type) { @@ -345,6 +358,19 @@ void FileFilterIndex::manageRatingsEntryInIndex(FileData* game, bool remove) manageIndexEntry(&ratingsIndexAllKeys, key, remove); } +void FileFilterIndex::manageFavoritesEntryInIndex(FileData* game, bool remove) +{ + // flag for including unknowns + bool includeUnknown = INCLUDE_UNKNOWN; + std::string key = getIndexableKey(game, FAVORITES_FILTER, false); + if (!includeUnknown && key == UNKNOWN_LABEL) { + // no valid favorites info found + return; + } + + manageIndexEntry(&favoritesIndexAllKeys, key, remove); +} + void FileFilterIndex::manageIndexEntry(std::map* index, std::string key, bool remove) { bool includeUnknown = INCLUDE_UNKNOWN; if (!includeUnknown && key == UNKNOWN_LABEL) @@ -354,7 +380,7 @@ void FileFilterIndex::manageIndexEntry(std::map* index, std::s if (index->find(key) == index->end()) { // this shouldn't happen - LOG(LogError) << "Couldn't find entry in index! " << key; + LOG(LogInfo) << "Couldn't find entry in index! " << key; } else { diff --git a/es-app/src/FileFilterIndex.h b/es-app/src/FileFilterIndex.h index 7402c1e0e..d1e62d948 100644 --- a/es-app/src/FileFilterIndex.h +++ b/es-app/src/FileFilterIndex.h @@ -15,7 +15,8 @@ enum FilterIndexType GENRE_FILTER, PLAYER_FILTER, PUBDEV_FILTER, - RATINGS_FILTER + RATINGS_FILTER, + FAVORITES_FILTER }; struct FilterDataDecl @@ -41,10 +42,8 @@ public: void clearAllFilters(); void debugPrintIndexes(); bool showFile(FileData* game); - bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings); }; + bool isFiltered() { return (filterByGenre || filterByPlayers || filterByPubDev || filterByRatings || filterByFavorites); }; bool isKeyBeingFilteredBy(std::string key, FilterIndexType type); - std::map* getGenreAllIndexedKeys() { return &genreIndexAllKeys; }; - std::vector* getGenreFilteredKeys() { return &genreIndexFilteredKeys; }; std::vector& getFilterDataDecls(); private: std::vector filterDataDecl; @@ -54,6 +53,7 @@ private: void managePlayerEntryInIndex(FileData* game, bool remove = false); void managePubDevEntryInIndex(FileData* game, bool remove = false); void manageRatingsEntryInIndex(FileData* game, bool remove = false); + void manageFavoritesEntryInIndex(FileData* game, bool remove = false); void manageIndexEntry(std::map* index, std::string key, bool remove); @@ -63,16 +63,19 @@ private: bool filterByPlayers; bool filterByPubDev; bool filterByRatings; + bool filterByFavorites; std::map genreIndexAllKeys; std::map playersIndexAllKeys; std::map pubDevIndexAllKeys; std::map ratingsIndexAllKeys; + std::map favoritesIndexAllKeys; std::vector genreIndexFilteredKeys; std::vector playersIndexFilteredKeys; std::vector pubDevIndexFilteredKeys; std::vector ratingsIndexFilteredKeys; + std::vector favoritesIndexFilteredKeys; FileData* mRootFolder; diff --git a/es-app/src/FileSorts.cpp b/es-app/src/FileSorts.cpp index 7a431a9a9..101b913f1 100644 --- a/es-app/src/FileSorts.cpp +++ b/es-app/src/FileSorts.cpp @@ -3,8 +3,8 @@ namespace FileSorts { const FileData::SortType typesArr[] = { - FileData::SortType(&compareFileName, true, "filename, ascending"), - FileData::SortType(&compareFileName, false, "filename, descending"), + FileData::SortType(&compareName, true, "filename, ascending"), + FileData::SortType(&compareName, false, "filename, descending"), FileData::SortType(&compareRating, true, "rating, ascending"), FileData::SortType(&compareRating, false, "rating, descending"), @@ -28,16 +28,20 @@ namespace FileSorts FileData::SortType(&compareDeveloper, false, "developer, descending"), FileData::SortType(&comparePublisher, true, "publisher, ascending"), - FileData::SortType(&comparePublisher, false, "publisher, descending") + FileData::SortType(&comparePublisher, false, "publisher, descending"), + + FileData::SortType(&compareSystem, true, "system, ascending"), + FileData::SortType(&compareSystem, false, "system, descending") }; const std::vector SortTypes(typesArr, typesArr + sizeof(typesArr)/sizeof(typesArr[0])); //returns if file1 should come before file2 - bool compareFileName(const FileData* file1, const FileData* file2) + bool compareName(const FileData* file1, const FileData* file2) { - std::string name1 = file1->getName(); - std::string name2 = file2->getName(); + // we compare the actual metadata name, as collection files have the system appended which messes up the order + std::string name1 = file1->metadata.get("name"); + std::string name2 = file2->metadata.get("name"); transform(name1.begin(), name1.end(), name1.begin(), ::toupper); transform(name2.begin(), name2.end(), name2.begin(), ::toupper); return name1.compare(name2) < 0; @@ -62,9 +66,11 @@ namespace FileSorts bool compareLastPlayed(const FileData* file1, const FileData* file2) { //only games have lastplayed metadata + // since it's stored as a POSIX string (YYYYMMDDTHHMMSS,fffffffff), we can compare as a string + // as it's a lot faster than the time casts and then time comparisons if(file1->metadata.getType() == GAME_METADATA && file2->metadata.getType() == GAME_METADATA) { - return (file1)->metadata.getTime("lastplayed") < (file2)->metadata.getTime("lastplayed"); + return (file1)->metadata.get("lastplayed") < (file2)->metadata.get("lastplayed"); } return false; @@ -106,4 +112,13 @@ namespace FileSorts transform(publisher2.begin(), publisher2.end(), publisher2.begin(), ::toupper); return publisher1.compare(publisher2) < 0; } + + bool compareSystem(const FileData* file1, const FileData* file2) + { + std::string system1 = file1->getSystemName(); + std::string system2 = file2->getSystemName(); + transform(system1.begin(), system1.end(), system1.begin(), ::toupper); + transform(system2.begin(), system2.end(), system2.begin(), ::toupper); + return system1.compare(system2) < 0; + } }; diff --git a/es-app/src/FileSorts.h b/es-app/src/FileSorts.h index b27925097..ac24a1db3 100644 --- a/es-app/src/FileSorts.h +++ b/es-app/src/FileSorts.h @@ -2,10 +2,11 @@ #include #include "FileData.h" +#include "SystemData.h" namespace FileSorts { - bool compareFileName(const FileData* file1, const FileData* file2); + bool compareName(const FileData* file1, const FileData* file2); bool compareRating(const FileData* file1, const FileData* file2); bool compareTimesPlayed(const FileData* file1, const FileData* fil2); bool compareLastPlayed(const FileData* file1, const FileData* file2); @@ -14,6 +15,7 @@ namespace FileSorts bool compareGenre(const FileData* file1, const FileData* file2); bool compareDeveloper(const FileData* file1, const FileData* file2); bool comparePublisher(const FileData* file1, const FileData* file2); + bool compareSystem(const FileData* file1, const FileData* file2); extern const std::vector SortTypes; }; diff --git a/es-app/src/Gamelist.cpp b/es-app/src/Gamelist.cpp index b6da7018a..cae840264 100644 --- a/es-app/src/Gamelist.cpp +++ b/es-app/src/Gamelist.cpp @@ -54,7 +54,7 @@ FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& pa return NULL; } - FileData* file = new FileData(type, path, system); + FileData* file = new FileData(type, path, system->getSystemEnvData(), system); treeNode->addChild(file); return file; } @@ -70,7 +70,7 @@ FileData* findOrCreateFile(SystemData* system, const boost::filesystem::path& pa } // create missing folder - FileData* folder = new FileData(FOLDER, treeNode->getPath().stem() / *path_it, system); + FileData* folder = new FileData(FOLDER, treeNode->getPath().stem() / *path_it, system->getSystemEnvData(), system); treeNode->addChild(folder); treeNode = folder; } diff --git a/es-app/src/MetaData.cpp b/es-app/src/MetaData.cpp index 8dd6426ef..2eb1e5eff 100644 --- a/es-app/src/MetaData.cpp +++ b/es-app/src/MetaData.cpp @@ -5,38 +5,39 @@ namespace fs = boost::filesystem; -MetaDataDecl gameDecls[] = { - // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd - {"name", MD_STRING, "", false, "name", "enter game name"}, - {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"}, - {"image", MD_PATH, "", false, "image", "enter path to image"}, - {"video", MD_PATH , "", false, "video", "enter path to video"}, - {"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"}, - {"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"}, - {"rating", MD_RATING, "0.000000", false, "rating", "enter rating"}, - {"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"}, - {"developer", MD_STRING, "unknown", false, "developer", "enter game developer"}, - {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"}, - {"genre", MD_STRING, "unknown", false, "genre", "enter game genre"}, - {"players", MD_INT, "1", false, "players", "enter number of players"}, - {"playcount", MD_INT, "0", true, "play count", "enter number of times played"}, - {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"} +MetaDataDecl gameDecls[] = { + // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd + {"name", MD_STRING, "", false, "name", "enter game name"}, + {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"}, + {"image", MD_PATH, "", false, "image", "enter path to image"}, + {"video", MD_PATH , "", false, "video", "enter path to video"}, + {"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"}, + {"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"}, + {"rating", MD_RATING, "0.000000", false, "rating", "enter rating"}, + {"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"}, + {"developer", MD_STRING, "unknown", false, "developer", "enter game developer"}, + {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"}, + {"genre", MD_STRING, "unknown", false, "genre", "enter game genre"}, + {"players", MD_INT, "1", false, "players", "enter number of players"}, + {"favorite", MD_BOOL, "false", false, "favorite", "enter favorite off/on"}, + {"playcount", MD_INT, "0", true, "play count", "enter number of times played"}, + {"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"} }; const std::vector gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0])); -MetaDataDecl folderDecls[] = { - {"name", MD_STRING, "", false, "name", "enter game name"}, - {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"}, - {"image", MD_PATH, "", false, "image", "enter path to image"}, - {"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"}, - {"video", MD_PATH, "", false, "video", "enter path to video"}, - {"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"}, - {"rating", MD_RATING, "0.000000", false, "rating", "enter rating"}, - {"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"}, - {"developer", MD_STRING, "unknown", false, "developer", "enter game developer"}, - {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"}, - {"genre", MD_STRING, "unknown", false, "genre", "enter game genre"}, - {"players", MD_INT, "1", false, "players", "enter number of players"} +MetaDataDecl folderDecls[] = { + {"name", MD_STRING, "", false, "name", "enter game name"}, + {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"}, + {"image", MD_PATH, "", false, "image", "enter path to image"}, + {"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"}, + {"video", MD_PATH, "", false, "video", "enter path to video"}, + {"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"}, + {"rating", MD_RATING, "0.000000", false, "rating", "enter rating"}, + {"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"}, + {"developer", MD_STRING, "unknown", false, "developer", "enter game developer"}, + {"publisher", MD_STRING, "unknown", false, "publisher", "enter game publisher"}, + {"genre", MD_STRING, "unknown", false, "genre", "enter game genre"}, + {"players", MD_INT, "1", false, "players", "enter number of players"} }; const std::vector folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0])); diff --git a/es-app/src/MetaData.h b/es-app/src/MetaData.h index f9b867450..62c9869e6 100644 --- a/es-app/src/MetaData.h +++ b/es-app/src/MetaData.h @@ -13,6 +13,7 @@ enum MetaDataType MD_STRING, MD_INT, MD_FLOAT, + MD_BOOL, //specialized types MD_MULTILINE_STRING, diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 41efa64cb..91b55c30d 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -5,8 +5,6 @@ #include #include #include "Renderer.h" -#include "AudioManager.h" -#include "VolumeControl.h" #include "Log.h" #include "InputManager.h" #include @@ -17,45 +15,38 @@ std::vector SystemData::sSystemVector; namespace fs = boost::filesystem; -SystemData::SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::vector& extensions, - const std::string& command, const std::vector& platformIds, const std::string& themeFolder) +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) { - mName = name; - mFullName = fullName; - mStartPath = startPath; - - //expand home symbol if the startpath contains ~ - if(mStartPath[0] == '~') - { - mStartPath.erase(0, 1); - mStartPath.insert(0, getHomePath()); - } - - mSearchExtensions = extensions; - mLaunchCommand = command; - mPlatformIds = platformIds; - mThemeFolder = themeFolder; - mFilterIndex = new FileFilterIndex(); - mRootFolder = new FileData(FOLDER, mStartPath, this); - mRootFolder->metadata.set("name", mFullName); + // 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); - if(!Settings::getInstance()->getBool("ParseGamelistOnly")) - populateFolder(mRootFolder); + if(!Settings::getInstance()->getBool("ParseGamelistOnly")) + populateFolder(mRootFolder); - if(!Settings::getInstance()->getBool("IgnoreGamelist")) - parseGamelist(this); - - mRootFolder->sort(FileSorts::SortTypes.at(0)); + if(!Settings::getInstance()->getBool("IgnoreGamelist")) + parseGamelist(this); + mRootFolder->sort(FileSorts::SortTypes.at(0)); + } + else + { + // virtual systems are updated afterwards, we're just creating the data structure + mRootFolder = new FileData(FOLDER, "" + name, mEnvData, this); + } + setIsGameSystemStatus(); loadTheme(); } SystemData::~SystemData() { //save changed game data back to xml - if(!Settings::getInstance()->getBool("IgnoreGamelist") && Settings::getInstance()->getBool("SaveGamelistsOnExit")) + if(!Settings::getInstance()->getBool("IgnoreGamelist") && Settings::getInstance()->getBool("SaveGamelistsOnExit") && !mIsCollectionSystem) { updateGamelist(this); } @@ -64,87 +55,12 @@ SystemData::~SystemData() delete mFilterIndex; } - -std::string strreplace(std::string str, const std::string& replace, const std::string& with) +void SystemData::setIsGameSystemStatus() { - size_t pos; - while((pos = str.find(replace)) != std::string::npos) - str = str.replace(pos, replace.length(), with.c_str(), with.length()); - - return str; -} - -// plaform-specific escape path function -// on windows: just puts the path in quotes -// everything else: assume bash and escape special characters with backslashes -std::string escapePath(const boost::filesystem::path& path) -{ -#ifdef WIN32 - // windows escapes stuff by just putting everything in quotes - return '"' + fs::path(path).make_preferred().string() + '"'; -#else - // a quick and dirty way to insert a backslash before most characters that would mess up a bash path - std::string pathStr = path.string(); - - const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; - for(unsigned int i = 0; i < pathStr.length(); i++) - { - char c; - unsigned int charNum = 0; - do { - c = invalidChars[charNum]; - if(pathStr[i] == c) - { - pathStr.insert(i, "\\"); - i++; - break; - } - charNum++; - } while(c != '\0'); - } - - return pathStr; -#endif -} - -void SystemData::launchGame(Window* window, FileData* game) -{ - LOG(LogInfo) << "Attempting to launch game..."; - - AudioManager::getInstance()->deinit(); - VolumeControl::getInstance()->deinit(); - window->deinit(); - - std::string command = mLaunchCommand; - - const std::string rom = escapePath(game->getPath()); - const std::string basename = game->getPath().stem().string(); - const std::string rom_raw = fs::path(game->getPath()).make_preferred().string(); - - command = strreplace(command, "%ROM%", rom); - command = strreplace(command, "%BASENAME%", basename); - command = strreplace(command, "%ROM_RAW%", rom_raw); - - LOG(LogInfo) << " " << command; - int exitCode = runSystemCommand(command); - - if(exitCode != 0) - { - LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!"; - } - - window->init(); - VolumeControl::getInstance()->init(); - AudioManager::getInstance()->init(); - window->normalizeNextUpdate(); - - //update number of times the game has been launched - int timesPlayed = game->metadata.getInt("playcount") + 1; - game->metadata.set("playcount", std::to_string(static_cast(timesPlayed))); - - //update last played time - boost::posix_time::ptime time = boost::posix_time::second_clock::universal_time(); - game->metadata.setTime("lastplayed", time); + // 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) @@ -187,9 +103,9 @@ void SystemData::populateFolder(FileData* folder) //see issue #75: https://github.com/Aloshi/EmulationStation/issues/75 isGame = false; - if(std::find(mSearchExtensions.begin(), mSearchExtensions.end(), extension) != mSearchExtensions.end()) + if(std::find(mEnvData->mSearchExtensions.begin(), mEnvData->mSearchExtensions.end(), extension) != mEnvData->mSearchExtensions.end()) { - FileData* newGame = new FileData(GAME, filePath.generic_string(), this); + FileData* newGame = new FileData(GAME, filePath.generic_string(), mEnvData, this); folder->addChild(newGame); isGame = true; } @@ -197,7 +113,7 @@ void SystemData::populateFolder(FileData* folder) //add directories that also do not match an extension as folders if(!isGame && fs::is_directory(filePath)) { - FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), this); + FileData* newFolder = new FileData(FOLDER, filePath.generic_string(), mEnvData, this); populateFolder(newFolder); //ignore folders that do not contain games @@ -313,7 +229,21 @@ bool SystemData::loadConfig() boost::filesystem::path genericPath(path); path = genericPath.generic_string(); - SystemData* newSys = new SystemData(name, fullname, path, extensions, cmd, platformIds, themeFolder); + //expand home symbol if the startpath contains ~ + if(path[0] == '~') + { + path.erase(0, 1); + path.insert(0, getHomePath()); + } + + //create the system runtime environment data + SystemEnvironmentData* envData = new SystemEnvironmentData; + envData->mStartPath = path; + envData->mSearchExtensions = extensions; + envData->mLaunchCommand = cmd; + envData->mPlatformIds = platformIds; + + SystemData* newSys = new SystemData(name, fullname, envData, themeFolder); if(newSys->getRootFolder()->getChildrenByFilename().size() == 0) { LOG(LogWarning) << "System \"" << name << "\" has no games! Ignoring it."; @@ -322,6 +252,7 @@ bool SystemData::loadConfig() sSystemVector.push_back(newSys); } } + CollectionSystemManager::get()->loadCollectionSystems(); return true; } @@ -432,6 +363,44 @@ unsigned int SystemData::getGameCount() const return mRootFolder->getFilesRecursive(GAME).size(); } +SystemData* SystemData::getRandomSystem() +{ + // this is a bit brute force. It might be more efficient to just to a while (!gameSystem) do random again... + unsigned int total = 0; + for(auto it = sSystemVector.begin(); it != sSystemVector.end(); it++) + { + if ((*it)->isGameSystem()) + total ++; + } + + // get random number in range + int target = std::round(((double)std::rand() / (double)RAND_MAX) * total); + + for (auto it = sSystemVector.begin(); it != sSystemVector.end(); it++) + { + if ((*it)->isGameSystem()) + { + if (target >= 0) + { + target--; + } + else + { + return (*it); + } + } + } +} + +FileData* SystemData::getRandomGame() +{ + std::vector list = mRootFolder->getFilesRecursive(GAME, true); + unsigned int total = list.size(); + // get random number in range + int target = std::round(((double)std::rand() / (double)RAND_MAX) * total); + return list.at(target); +} + unsigned int SystemData::getDisplayedGameCount() const { return mRootFolder->getFilesRecursive(GAME, true).size(); diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index 7916f1825..cb44a1c11 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -8,23 +8,31 @@ #include "PlatformId.h" #include "ThemeData.h" #include "FileFilterIndex.h" +#include "CollectionSystemManager.h" + +struct SystemEnvironmentData +{ + std::string mStartPath; + std::vector mSearchExtensions; + std::string mLaunchCommand; + std::vector mPlatformIds; +}; class SystemData { public: - SystemData(const std::string& name, const std::string& fullName, const std::string& startPath, const std::vector& extensions, - const std::string& command, const std::vector& platformIds, const std::string& themeFolder); + 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 mStartPath; } - inline const std::vector& getExtensions() const { return mSearchExtensions; } + inline const std::string& getStartPath() const { return mEnvData->mStartPath; } + inline const std::vector& getExtensions() const { return mEnvData->mSearchExtensions; } inline const std::string& getThemeFolder() const { return mThemeFolder; } - - inline const std::vector& getPlatformIds() const { return mPlatformIds; } - inline bool hasPlatformId(PlatformIds::PlatformId id) { return std::find(mPlatformIds.begin(), mPlatformIds.end(), id) != mPlatformIds.end(); } + inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } + inline const std::vector& getPlatformIds() const { return mEnvData->mPlatformIds; } + inline bool hasPlatformId(PlatformIds::PlatformId id) { if (!mEnvData) return false; return std::find(mEnvData->mPlatformIds.begin(), mEnvData->mPlatformIds.end(), id) != mEnvData->mPlatformIds.end(); } inline const std::shared_ptr& getTheme() const { return mTheme; } @@ -35,8 +43,6 @@ public: unsigned int getGameCount() const; unsigned int getDisplayedGameCount() const; - void launchGame(Window* window, FileData* game); - 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. static void writeExampleConfig(const std::string& path); @@ -46,7 +52,8 @@ public: inline std::vector::const_iterator getIterator() const { return std::find(sSystemVector.begin(), sSystemVector.end(), this); }; inline std::vector::const_reverse_iterator getRevIterator() const { return std::find(sSystemVector.rbegin(), sSystemVector.rend(), this); }; - + inline bool isCollection() { return mIsCollectionSystem; }; + inline bool isGameSystem() { return mIsGameSystem; } inline SystemData* getNext() const { auto it = getIterator(); @@ -63,22 +70,25 @@ public: return *it; } + static SystemData* getRandomSystem(); + FileData* getRandomGame(); + // Load or re-load theme. void loadTheme(); FileFilterIndex* getIndex() { return mFilterIndex; }; private: + bool mIsCollectionSystem; + bool mIsGameSystem; std::string mName; std::string mFullName; - std::string mStartPath; - std::vector mSearchExtensions; - std::string mLaunchCommand; - std::vector mPlatformIds; + SystemEnvironmentData* mEnvData; std::string mThemeFolder; std::shared_ptr mTheme; void populateFolder(FileData* folder); + void setIsGameSystemStatus(); FileFilterIndex* mFilterIndex; diff --git a/es-app/src/SystemScreenSaver.cpp b/es-app/src/SystemScreenSaver.cpp index 242d3ef9c..6136e6d16 100644 --- a/es-app/src/SystemScreenSaver.cpp +++ b/es-app/src/SystemScreenSaver.cpp @@ -148,23 +148,26 @@ void SystemScreenSaver::countVideos() std::vector:: iterator it; for (it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); ++it) { - pugi::xml_document doc; - pugi::xml_node root; - std::string xmlReadPath = (*it)->getGamelistPath(false); - - if(boost::filesystem::exists(xmlReadPath)) + if (!(*it)->isCollection()) { - pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str()); - if (!result) - continue; - root = doc.child("gameList"); - if (!root) - continue; - for(pugi::xml_node fileNode = root.child("game"); fileNode; fileNode = fileNode.next_sibling("game")) + pugi::xml_document doc; + pugi::xml_node root; + std::string xmlReadPath = (*it)->getGamelistPath(false); + + if(boost::filesystem::exists(xmlReadPath)) { - pugi::xml_node videoNode = fileNode.child("video"); - if (videoNode) - ++mVideoCount; + pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str()); + if (!result) + continue; + root = doc.child("gameList"); + if (!root) + continue; + for(pugi::xml_node fileNode = root.child("game"); fileNode; fileNode = fileNode.next_sibling("game")) + { + pugi::xml_node videoNode = fileNode.child("video"); + if (videoNode) + ++mVideoCount; + } } } } diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp new file mode 100644 index 000000000..23833a114 --- /dev/null +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -0,0 +1,105 @@ +#include "guis/GuiCollectionSystemsOptions.h" +#include "guis/GuiMsgBox.h" +#include "Settings.h" +#include "views/ViewController.h" + +#include "components/TextComponent.h" +#include "components/OptionListComponent.h" + +GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(Window* window) : GuiComponent(window), mMenu(window, "GAME COLLECTION SETTINGS") +{ + initializeMenu(); +} + +void GuiCollectionSystemsOptions::initializeMenu() +{ + addChild(&mMenu); + + // get virtual systems + + addSystemsToMenu(); + + mMenu.addButton("BACK", "back", std::bind(&GuiCollectionSystemsOptions::applySettings, this)); + + mMenu.setPosition((Renderer::getScreenWidth() - mMenu.getSize().x()) / 2, Renderer::getScreenHeight() * 0.15f); +} + +GuiCollectionSystemsOptions::~GuiCollectionSystemsOptions() +{ + //mSystemOptions.clear(); +} + +void GuiCollectionSystemsOptions::addSystemsToMenu() +{ + + std::map vSystems = CollectionSystemManager::get()->getCollectionSystems(); + + autoOptionList = std::make_shared< OptionListComponent >(mWindow, "SELECT COLLECTIONS", true); + + // add Systems + ComponentListRow row; + + for(std::map::iterator it = vSystems.begin() ; it != vSystems.end() ; it++ ) + { + autoOptionList->add(it->second.decl.longName, it->second.decl.name, it->second.isEnabled); + } + mMenu.addWithLabel("AUTOMATIC COLLECTIONS", autoOptionList); +} + +void GuiCollectionSystemsOptions::applySettings() +{ + std::string out = commaStringToVector(autoOptionList->getSelectedObjects()); + std::string prev = Settings::getInstance()->getString("CollectionSystemsAuto"); + if (out != "" && !CollectionSystemManager::get()->isThemeAutoCompatible()) + { + mWindow->pushGui(new GuiMsgBox(mWindow, + "Your theme does not support game collections. Please update your theme, or ensure that you use a theme that contains the folders:\n\n• auto-favorites\n• auto-lastplayed\n• auto-allgames\n\nDo you still want to enable collections?", + "YES", [this, out, prev] { + if (prev != out) + { + updateSettings(out); + } + delete this; }, + "NO", [this] { delete this; })); + } + else + { + if (prev != out) + { + updateSettings(out); + } + delete this; + } +} + +void GuiCollectionSystemsOptions::updateSettings(std::string newSettings) +{ + Settings::getInstance()->setString("CollectionSystemsAuto", newSettings); + Settings::getInstance()->saveFile(); + CollectionSystemManager::get()->loadEnabledListFromSettings(); + CollectionSystemManager::get()->updateSystemsList(); + ViewController::get()->goToStart(); + ViewController::get()->reloadAll(); +} + +bool GuiCollectionSystemsOptions::input(InputConfig* config, Input input) +{ + bool consumed = GuiComponent::input(config, input); + if(consumed) + return true; + + if(config->isMappedTo("b", input) && input.value != 0) + { + applySettings(); + } + + + return false; +} + +std::vector GuiCollectionSystemsOptions::getHelpPrompts() +{ + std::vector prompts = mMenu.getHelpPrompts(); + prompts.push_back(HelpPrompt("b", "back")); + return prompts; +} diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.h b/es-app/src/guis/GuiCollectionSystemsOptions.h new file mode 100644 index 000000000..739aecd1b --- /dev/null +++ b/es-app/src/guis/GuiCollectionSystemsOptions.h @@ -0,0 +1,31 @@ +#pragma once + +#include "GuiComponent.h" +#include "SystemData.h" +#include "components/MenuComponent.h" +#include "CollectionSystemManager.h" +#include "Log.h" + + +template +class OptionListComponent; + + +class GuiCollectionSystemsOptions : public GuiComponent +{ +public: + GuiCollectionSystemsOptions(Window* window); + ~GuiCollectionSystemsOptions(); + bool input(InputConfig* config, Input input) override; + + virtual std::vector getHelpPrompts() override; + +private: + void initializeMenu(); + void applySettings(); + void addSystemsToMenu(); + void updateSettings(std::string newSettings); + std::shared_ptr< OptionListComponent > autoOptionList; + MenuComponent mMenu; + SystemData* mSystem; +}; diff --git a/es-app/src/guis/GuiFastSelect.cpp b/es-app/src/guis/GuiFastSelect.cpp index 31db35041..75b7fbe62 100644 --- a/es-app/src/guis/GuiFastSelect.cpp +++ b/es-app/src/guis/GuiFastSelect.cpp @@ -145,7 +145,7 @@ void GuiFastSelect::updateGameListCursor() // only skip by letter when the sort mode is alphabetical const FileData::SortType& sort = FileSorts::SortTypes.at(mSortId); - if(sort.comparisonFunction != &FileSorts::compareFileName) + if(sort.comparisonFunction != &FileSorts::compareName) return; // find the first entry in the list that either exactly matches our target letter or is beyond our target letter diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index d3afd0077..e8f579f75 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -2,6 +2,7 @@ #include "GuiMetaDataEd.h" #include "views/gamelist/IGameListView.h" #include "views/ViewController.h" +#include "CollectionSystemManager.h" GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window), mSystem(system), mMenu(window, "OPTIONS"), fromPlaceholder(false), mFiltersChanged(false) @@ -122,7 +123,8 @@ void GuiGamelistOptions::openGamelistFilter() void GuiGamelistOptions::openMetaDataEd() { // open metadata editor - FileData* file = getGamelist()->getCursor(); + // get the FileData that hosts the original metadata + FileData* file = getGamelist()->getCursor()->getSourceFileData(); ScraperSearchParams p; p.game = file; p.system = file->getSystem(); @@ -136,12 +138,13 @@ void GuiGamelistOptions::openMetaDataEd() else { deleteBtnFunc = [this, file] { - getGamelist()->remove(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, file->getPath().filename().string(), - std::bind(&IGameListView::onFileChanged, getGamelist(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); + std::bind(&IGameListView::onFileChanged, ViewController::get()->getGameListView(file->getSystem()).get(), file, FILE_METADATA_CHANGED), deleteBtnFunc)); } void GuiGamelistOptions::jumpToLetter() diff --git a/es-app/src/guis/GuiInfoPopup.cpp b/es-app/src/guis/GuiInfoPopup.cpp new file mode 100644 index 000000000..7fd78fd18 --- /dev/null +++ b/es-app/src/guis/GuiInfoPopup.cpp @@ -0,0 +1,114 @@ +#include "guis/GuiInfoPopup.h" +#include "Renderer.h" +#include "components/TextComponent.h" +#include "Log.h" + +GuiInfoPopup::GuiInfoPopup(Window* window, std::string message, int duration) : + GuiComponent(window), mMessage(message), mDuration(duration), running(true) +{ + mFrame = new NinePatchComponent(window); + float maxWidth = Renderer::getScreenWidth() * 0.9f; + float maxHeight = Renderer::getScreenHeight() * 0.2f; + + std::shared_ptr s = std::make_shared(mWindow, + "", + Font::get(FONT_SIZE_MINI), + 0x444444FF, + ALIGN_CENTER); + + // we do this to force the text container to resize and return an actual expected popup size + s->setSize(0,0); + s->setText(message); + mSize = s->getSize(); + + // confirm the size isn't larger than the screen width, otherwise cap it + if (mSize.x() > maxWidth) { + s->setSize(maxWidth, mSize[1]); + mSize[0] = maxWidth; + } + if (mSize.y() > maxHeight) { + s->setSize(mSize[0], maxHeight); + mSize[1] = maxHeight; + } + + // add a padding to the box + int paddingX = Renderer::getScreenWidth() * 0.03f; + int paddingY = Renderer::getScreenHeight() * 0.02f; + mSize[0] = mSize.x() + paddingX; + mSize[1] = mSize.y() + paddingY; + + float posX = Renderer::getScreenWidth()*0.5f - mSize.x()*0.5f; + float posY = Renderer::getScreenHeight() * 0.02f; + + setPosition(posX, posY, 0); + + mFrame->setImagePath(":/frame.png"); + mFrame->fitTo(mSize, Eigen::Vector3f::Zero(), Eigen::Vector2f(-32, -32)); + addChild(mFrame); + + // we only init the actual time when we first start to render + mStartTime = 0; + + mGrid = new ComponentGrid(window, Eigen::Vector2i(1, 3)); + mGrid->setSize(mSize); + mGrid->setEntry(s, Eigen::Vector2i(0, 1), false, true); + addChild(mGrid); +} + +GuiInfoPopup::~GuiInfoPopup() +{ + +} + +void GuiInfoPopup::render(const Eigen::Affine3f& parentTrans) +{ + // we use identity as we want to render on a specific window position, not on the view + Eigen::Affine3f trans = getTransform() * Eigen::Affine3f::Identity(); + if(running && updateState()) + { + // if we're still supposed to be rendering it + Renderer::setMatrix(trans); + renderChildren(trans); + } +} + +bool GuiInfoPopup::updateState() +{ + int curTime = SDL_GetTicks(); + + // we only init the actual time when we first start to render + if(mStartTime == 0) + { + mStartTime = curTime; + } + + // compute fade in effect + if (curTime - mStartTime > mDuration) + { + // we're past the popup duration, no need to render + running = false; + return false; + } + else if (curTime < mStartTime) { + // if SDL reset + running = false; + return false; + } + else if (curTime - mStartTime <= 500) { + alpha = ((curTime - mStartTime)*255/500); + } + else if (curTime - mStartTime < mDuration - 500) + { + alpha = 255; + } + else + { + alpha = ((-(curTime - mStartTime - mDuration)*255)/500); + } + mGrid->setOpacity(alpha); + + // apply fade in effect to popup frame + mFrame->setEdgeColor(0xFFFFFF00 | (unsigned char)(alpha)); + mFrame->setCenterColor(0xFFFFFF00 | (unsigned char)(alpha)); + return true; +} \ No newline at end of file diff --git a/es-app/src/guis/GuiInfoPopup.h b/es-app/src/guis/GuiInfoPopup.h new file mode 100644 index 000000000..6b899680e --- /dev/null +++ b/es-app/src/guis/GuiInfoPopup.h @@ -0,0 +1,27 @@ +#pragma once + +#include "GuiComponent.h" +#include "components/NinePatchComponent.h" +#include "components/ComponentGrid.h" +#include "Window.h" +#include "Log.h" + + + +class GuiInfoPopup : public GuiComponent, public Window::InfoPopup +{ +public: + GuiInfoPopup(Window* window, std::string message, int duration); + ~GuiInfoPopup(); + void render(const Eigen::Affine3f& parentTrans) override; + inline void stop() { running = false; }; +private: + std::string mMessage; + int mDuration; + int alpha; + bool updateState(); + int mStartTime; + ComponentGrid* mGrid; + NinePatchComponent* mFrame; + bool running; +}; diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 43ced8330..f645b8ba9 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -7,6 +7,7 @@ #include "guis/GuiMsgBox.h" #include "guis/GuiSettings.h" #include "guis/GuiScreensaverOptions.h" +#include "guis/GuiCollectionSystemsOptions.h" #include "guis/GuiScraperStart.h" #include "guis/GuiDetectDevice.h" #include "views/ViewController.h" @@ -238,6 +239,9 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN mWindow->pushGui(s); }); + addEntry("GAME COLLECTION SETTINGS", 0x777777FF, true, + [this] { openCollectionSystemSettings(); + }); addEntry("OTHER SETTINGS", 0x777777FF, true, [this] { auto s = new GuiSettings(mWindow, "OTHER SETTINGS"); @@ -364,13 +368,20 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN addChild(&mMenu); addChild(&mVersion); + /*int menuWidth = Renderer::getScreenWidth() * 0.4f; + int menuHeight = Renderer::getScreenHeight() * 0.74f; + mMenu.setSize(menuWidth, menuHeight);*/ + setSize(mMenu.getSize()); - setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, Renderer::getScreenHeight() * 0.15f); + setPosition((Renderer::getScreenWidth() - mSize.x()) / 2, Renderer::getScreenHeight() * 0.13f); } void GuiMenu::openScreensaverOptions() { - GuiScreensaverOptions* ggf = new GuiScreensaverOptions(mWindow, "VIDEO SCREENSAVER"); - mWindow->pushGui(ggf); + mWindow->pushGui(new GuiScreensaverOptions(mWindow, "VIDEO SCREENSAVER")); +} + +void GuiMenu::openCollectionSystemSettings() { + mWindow->pushGui(new GuiCollectionSystemsOptions(mWindow)); } void GuiMenu::onSizeChanged() diff --git a/es-app/src/guis/GuiMenu.h b/es-app/src/guis/GuiMenu.h index 42862d24a..8d99f14e4 100644 --- a/es-app/src/guis/GuiMenu.h +++ b/es-app/src/guis/GuiMenu.h @@ -17,6 +17,7 @@ public: private: void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function& func); void openScreensaverOptions(); + void openCollectionSystemSettings(); MenuComponent mMenu; TextComponent mVersion; }; diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 586b3279f..e6a722ce0 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -11,6 +11,7 @@ #include "components/TextEditComponent.h" #include "components/DateTimeComponent.h" #include "components/RatingComponent.h" +#include "components/SwitchComponent.h" #include "guis/GuiTextEditPopup.h" using namespace Eigen; @@ -59,6 +60,12 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector switch(iter->type) { + case MD_BOOL: + { + ed = std::make_shared(window); + row.addElement(ed, false, true); + break; + } case MD_RATING: { ed = std::make_shared(window); @@ -177,7 +184,6 @@ void GuiMetaDataEd::save() { if(mMetaDataDecl.at(i).isStatistic) continue; - mMetaData->set(mMetaDataDecl.at(i).key, mEditors.at(i)->getValue()); } @@ -186,6 +192,9 @@ void GuiMetaDataEd::save() if(mSavedCallback) mSavedCallback(); + + // update respective Collection Entries + CollectionSystemManager::get()->updateCollectionSystems(mScraperParams.game); } void GuiMetaDataEd::fetch() diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 0128089be..120febc25 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -225,6 +225,7 @@ int main(int argc, char* argv[]) Window window; SystemScreenSaver screensaver(&window); ViewController::init(&window); + CollectionSystemManager::init(&window); window.pushGui(ViewController::get()); if(!scrape_cmdline) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 3252c8a5f..2646a1d36 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -149,7 +149,10 @@ bool SystemView::input(InputConfig* config, Input input) } if (config->isMappedTo("x", input)) { - ViewController::get()->goToRandomGame(); + // get random system + // go to system + setCursor(SystemData::getRandomSystem()); + //ViewController::get()->goToRandomGame(); return true; } }else{ @@ -216,7 +219,7 @@ void SystemView::onCursorChanged(const CursorState& state) setAnimation(infoFadeOut, 0, [this, gameCount] { std::stringstream ss; - if (getSelected()->getName() == "retropie") + if (!getSelected()->isGameSystem()) ss << "CONFIGURATION"; // only display a game count if there are at least 2 games else if(gameCount > 1) diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 7e3c242b6..625aadf2f 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -124,7 +124,7 @@ void ViewController::goToRandomGame() unsigned int total = 0; for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) { - if ((*it)->getName() != "retropie") + if ((*it)->isGameSystem()) total += (*it)->getDisplayedGameCount(); } @@ -133,7 +133,7 @@ void ViewController::goToRandomGame() for (auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++) { - if ((*it)->getName() != "retropie") + if ((*it)->isGameSystem()) { if ((target - (int)(*it)->getDisplayedGameCount()) >= 0) { @@ -228,6 +228,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) center += mCurrentView->getPosition(); 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"); @@ -241,7 +242,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) }; setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc] { - game->getSystem()->launchGame(mWindow, game); + game->launchGame(mWindow); mLockInput = false; setAnimation(new LambdaAnimation(fadeFunc, 800), 0, nullptr, true); this->onFileChanged(game, FILE_METADATA_CHANGED); @@ -250,7 +251,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) // 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->getSystem()->launchGame(mWindow, game); + game->launchGame(mWindow); mCamera = origCamera; mLockInput = false; setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, nullptr, true); @@ -259,7 +260,7 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center) } else { setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, [this, origCamera, center, game] { - game->getSystem()->launchGame(mWindow, game); + game->launchGame(mWindow); mCamera = origCamera; mLockInput = false; setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 10), 0, nullptr, true); @@ -436,14 +437,12 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme) if(reloadTheme) system->loadTheme(); - std::shared_ptr newView = getGameListView(system); // to counter having come from a placeholder if (!cursor->isPlaceHolder()) { newView->setCursor(cursor); } - if(isCurrent) mCurrentView = newView; diff --git a/es-app/src/views/gamelist/BasicGameListView.cpp b/es-app/src/views/gamelist/BasicGameListView.cpp index d7fe8ec2b..1202da118 100644 --- a/es-app/src/views/gamelist/BasicGameListView.cpp +++ b/es-app/src/views/gamelist/BasicGameListView.cpp @@ -53,9 +53,7 @@ void BasicGameListView::populateList(const std::vector& files) } else { - // empty list - add a placeholder - FileData* placeholder = new FileData(PLACEHOLDER, "", this->mRoot->getSystem()); - mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER)); + addPlaceholder(); } } @@ -66,9 +64,7 @@ FileData* BasicGameListView::getCursor() void BasicGameListView::setCursor(FileData* cursor) { - if (cursor->isPlaceHolder()) - return; - if(!mList.setCursor(cursor)) + if(!mList.setCursor(cursor) && (!cursor->isPlaceHolder())) { populateList(cursor->getParent()->getChildrenListToDisplay()); mList.setCursor(cursor); @@ -95,17 +91,26 @@ void BasicGameListView::setCursor(FileData* cursor) } } +void BasicGameListView::addPlaceholder() +{ + // empty list - add a placeholder + FileData* placeholder = new FileData(PLACEHOLDER, "", this->mRoot->getSystem()->getSystemEnvData(), this->mRoot->getSystem()); + mList.add(placeholder->getName(), placeholder, (placeholder->getType() == PLACEHOLDER)); +} + void BasicGameListView::launch(FileData* game) { ViewController::get()->launch(game); } -void BasicGameListView::remove(FileData *game) +void BasicGameListView::remove(FileData *game, bool deleteFile) { - boost::filesystem::remove(game->getPath()); // actually delete the file on the filesystem + if (deleteFile) + boost::filesystem::remove(game->getPath()); // actually delete the file on the filesystem + FileData* parent = game->getParent(); if (getCursor() == game) // Select next element in list, or prev if none { - std::vector siblings = game->getParent()->getChildren(); + std::vector siblings = parent->getChildrenListToDisplay(); auto gameIter = std::find(siblings.begin(), siblings.end(), game); auto gamePos = std::distance(siblings.begin(), gameIter); if (gameIter != siblings.end()) @@ -118,8 +123,13 @@ void BasicGameListView::remove(FileData *game) } } } + mList.remove(game); + if(mList.size() == 0) + { + addPlaceholder(); + } delete game; // remove before repopulating (removes from parent) - onFileChanged(game, FILE_REMOVED); // update the view, with game removed + onFileChanged(parent, FILE_REMOVED); // update the view, with game removed } std::vector BasicGameListView::getHelpPrompts() @@ -133,5 +143,7 @@ std::vector BasicGameListView::getHelpPrompts() prompts.push_back(HelpPrompt("b", "back")); prompts.push_back(HelpPrompt("select", "options")); prompts.push_back(HelpPrompt("x", "random")); + if(Settings::getInstance()->getString("CollectionSystemsAuto").find("favorites") != std::string::npos && mRoot->getSystem()->isGameSystem()) + prompts.push_back(HelpPrompt("y", "favorite")); return prompts; } diff --git a/es-app/src/views/gamelist/BasicGameListView.h b/es-app/src/views/gamelist/BasicGameListView.h index 00998cd8c..82a15a19a 100644 --- a/es-app/src/views/gamelist/BasicGameListView.h +++ b/es-app/src/views/gamelist/BasicGameListView.h @@ -23,7 +23,8 @@ public: protected: virtual void populateList(const std::vector& files) override; - virtual void remove(FileData* game) override; + virtual void remove(FileData* game, bool deleteFile) override; + virtual void addPlaceholder(); TextListComponent mList; }; diff --git a/es-app/src/views/gamelist/IGameListView.h b/es-app/src/views/gamelist/IGameListView.h index eb7610790..23f1bd78b 100644 --- a/es-app/src/views/gamelist/IGameListView.h +++ b/es-app/src/views/gamelist/IGameListView.h @@ -32,7 +32,7 @@ public: virtual void setCursor(FileData*) = 0; virtual bool input(InputConfig* config, Input input) override; - virtual void remove(FileData* game) = 0; + virtual void remove(FileData* game, bool deleteFile) = 0; virtual const char* getName() const = 0; virtual void launch(FileData* game) = 0; diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index 617b61350..3b468f445 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -3,7 +3,9 @@ #include "Window.h" #include "views/ViewController.h" #include "Sound.h" +#include "Log.h" #include "Settings.h" +#include "CollectionSystemManager.h" ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGameListView(window, root), mHeaderText(window), mHeaderImage(window), mBackground(window) @@ -127,10 +129,31 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) } }else if (config->isMappedTo("x", input)) { - ViewController::get()->goToRandomGame(); + // go to random system game + setCursor(mRoot->getSystem()->getRandomGame()); + //ViewController::get()->goToRandomGame(); return true; + }else if (config->isMappedTo("y", input)) + { + if(Settings::getInstance()->getString("CollectionSystemsAuto").find("favorites") != std::string::npos && mRoot->getSystem()->isGameSystem()) + { + if(CollectionSystemManager::get()->toggleGameInCollection(getCursor(), "favorites")) + { + return true; + } + } } } return IGameListView::input(config, input); } + + + + + + + + + + diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 3f88c8758..4bd3a18b7 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -95,6 +95,7 @@ void Settings::setDefaults() mBoolMap["CaptionsCompatibility"] = true; // Audio out device for Video playback using OMX player. mStringMap["OMXAudioDev"] = "both"; + mStringMap["CollectionSystemsAuto"] = ""; // Audio out device for volume control #ifdef _RPI_ diff --git a/es-core/src/Util.cpp b/es-core/src/Util.cpp index 8d8810425..3a323ff0b 100644 --- a/es-core/src/Util.cpp +++ b/es-core/src/Util.cpp @@ -1,6 +1,7 @@ #include "Util.h" #include "resources/ResourceManager.h" #include "platform.h" +#include namespace fs = boost::filesystem; @@ -208,3 +209,103 @@ boost::posix_time::ptime string_to_ptime(const std::string& str, const std::stri return time; } + +std::string strreplace(std::string str, const std::string& replace, const std::string& with) +{ + size_t pos; + while((pos = str.find(replace)) != std::string::npos) + str = str.replace(pos, replace.length(), with.c_str(), with.length()); + + return str; +} + +// plaform-specific escape path function +// on windows: just puts the path in quotes +// everything else: assume bash and escape special characters with backslashes +std::string escapePath(const boost::filesystem::path& path) +{ +#ifdef WIN32 + // windows escapes stuff by just putting everything in quotes + return '"' + fs::path(path).make_preferred().string() + '"'; +#else + // a quick and dirty way to insert a backslash before most characters that would mess up a bash path + std::string pathStr = path.string(); + + const char* invalidChars = " '\"\\!$^&*(){}[]?;<>"; + for(unsigned int i = 0; i < pathStr.length(); i++) + { + char c; + unsigned int charNum = 0; + do { + c = invalidChars[charNum]; + if(pathStr[i] == c) + { + pathStr.insert(i, "\\"); + i++; + break; + } + charNum++; + } while(c != '\0'); + } + + return pathStr; +#endif +} + +std::string removeParenthesis(const std::string& str) +{ + // remove anything in parenthesis or brackets + // should be roughly equivalent to the regex replace "\((.*)\)|\[(.*)\]" with "" + // I would love to just use regex, but it's not worth pulling in another boost lib for one function that is used once + + std::string ret = str; + size_t start, end; + + static const int NUM_TO_REPLACE = 2; + static const char toReplace[NUM_TO_REPLACE*2] = { '(', ')', '[', ']' }; + + bool done = false; + while(!done) + { + done = true; + for(int i = 0; i < NUM_TO_REPLACE; i++) + { + end = ret.find_first_of(toReplace[i*2+1]); + start = ret.find_last_of(toReplace[i*2], end); + + if(start != std::string::npos && end != std::string::npos) + { + ret.erase(start, end - start + 1); + done = false; + } + } + } + + // also strip whitespace + end = ret.find_last_not_of(' '); + if(end != std::string::npos) + end++; + + ret = ret.substr(0, end); + + return ret; +} + +std::vector commaStringToVector(std::string commaString) +{ + // from a comma separated string, get a vector of strings + std::vector strs; + boost::split(strs, commaString, boost::is_any_of(",")); + return strs; +} + +std::string commaStringToVector(std::vector stringVector) +{ + std::string out = ""; + // from a vector of system names get comma separated string + for(std::vector::iterator it = stringVector.begin() ; it != stringVector.end() ; it++ ) + { + out = out + (out == "" ? "" : ",") + (*it); + } + return out; +} \ No newline at end of file diff --git a/es-core/src/Util.h b/es-core/src/Util.h index d26aee08b..f75d15214 100644 --- a/es-core/src/Util.h +++ b/es-core/src/Util.h @@ -34,3 +34,16 @@ boost::filesystem::path makeRelativePath(const boost::filesystem::path& path, co boost::filesystem::path resolvePath(const boost::filesystem::path& path, const boost::filesystem::path& relativeTo, bool allowHome); boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt = "%Y%m%dT%H%M%S%F%q"); + +std::string escapePath(const boost::filesystem::path& path); + +std::string strreplace(std::string str, const std::string& replace, const std::string& with); + +// Remove (.*) and [.*] from str +std::string removeParenthesis(const std::string& str); + +// split a comma-separated string into a vector +std::vector commaStringToVector(std::string commaString); + +// turn a vector of strings into a comma-separated string +std::string commaStringToVector(std::vector stringVector); \ No newline at end of file diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 4101df0df..957a7e1b2 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -10,7 +10,7 @@ #include "components/ImageComponent.h" Window::Window() : mNormalizeNextUpdate(false), mFrameTimeElapsed(0), mFrameCountElapsed(0), mAverageDeltaTime(10), - mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false) + mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false), mInfoPopup(NULL) { mHelp = new HelpComponent(this); mBackgroundOverlay = new ImageComponent(this); @@ -129,7 +129,7 @@ void Window::input(InputConfig* config, Input input) } return; } - else if(config->isMappedTo("start", input)) + else if(config->isMappedTo("start", input) && input.value != 0) { // launch game! cancelScreenSaver(); @@ -138,10 +138,10 @@ void Window::input(InputConfig* config, Input input) mSleeping = true; } } - else if(input.value != 0) + /*else if(input.value != 0) { return; - } + }*/ } } @@ -258,6 +258,11 @@ void Window::render() // Always call the screensaver render function regardless of whether the screensaver is active // or not because it may perform a fade on transition renderScreenSaver(); + + if(!mRenderScreenSaver && mInfoPopup) + { + mInfoPopup->render(transform); + } if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0) { diff --git a/es-core/src/Window.h b/es-core/src/Window.h index 7b92605e2..75b78e6c5 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -25,6 +25,13 @@ public: virtual void launchGame() = 0; }; + class InfoPopup { + public: + virtual void render(const Eigen::Affine3f& parentTrans) = 0; + virtual void stop() = 0; + virtual ~InfoPopup() {}; + }; + Window(); ~Window(); @@ -53,6 +60,8 @@ public: void setHelpPrompts(const std::vector& prompts, const HelpStyle& style); void setScreenSaver(ScreenSaver* screenSaver) { mScreenSaver = screenSaver; } + void setInfoPopup(InfoPopup* infoPopup) { delete mInfoPopup; mInfoPopup = infoPopup; } + inline void stopInfoPopup() { if (mInfoPopup) mInfoPopup->stop(); }; void startScreenSaver(); void cancelScreenSaver(); @@ -68,6 +77,7 @@ private: HelpComponent* mHelp; ImageComponent* mBackgroundOverlay; ScreenSaver* mScreenSaver; + InfoPopup* mInfoPopup; bool mRenderScreenSaver; std::vector mGuiStack; diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index 589bc6fab..58462dd9c 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -46,7 +46,7 @@ float MenuComponent::getButtonGridHeight() const void MenuComponent::updateSize() { - const float maxHeight = Renderer::getScreenHeight() * 0.7f; + const float maxHeight = Renderer::getScreenHeight() * 0.75f; float height = TITLE_HEIGHT + mList->getTotalRowHeight() + getButtonGridHeight() + 2; if(height > maxHeight) { diff --git a/es-core/src/components/SwitchComponent.cpp b/es-core/src/components/SwitchComponent.cpp index 07b425b25..c74a751b5 100644 --- a/es-core/src/components/SwitchComponent.cpp +++ b/es-core/src/components/SwitchComponent.cpp @@ -47,6 +47,23 @@ void SwitchComponent::setState(bool state) onStateChanged(); } +std::string SwitchComponent::getValue() const +{ + return mState ? "true" : "false"; +} + +void SwitchComponent::setValue(const std::string& statestring) +{ + if (statestring == "true") + { + mState = true; + }else + { + mState = false; + } + onStateChanged(); +} + void SwitchComponent::onStateChanged() { mImage.setImage(mState ? ":/on.svg" : ":/off.svg"); diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index e173762a7..46096b35a 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -16,6 +16,8 @@ public: bool getState() const; void setState(bool state); + std::string getValue() const; + void setValue(const std::string& statestring) override; virtual std::vector getHelpPrompts() override; diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index daaecbead..fcd45f50a 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -11,6 +11,7 @@ class TextCache; +#define FONT_SIZE_MINI ((unsigned int)(0.030f * Renderer::getScreenHeight())) #define FONT_SIZE_SMALL ((unsigned int)(0.035f * Renderer::getScreenHeight())) #define FONT_SIZE_MEDIUM ((unsigned int)(0.045f * Renderer::getScreenHeight())) #define FONT_SIZE_LARGE ((unsigned int)(0.085f * Renderer::getScreenHeight()))