From dc20a9e21bd00e94b394c9c9b061c6259e0fb707 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 23 Jan 2022 20:03:50 +0100 Subject: [PATCH] Cleaned up ThemeData a bit. --- es-app/src/CollectionSystemsManager.cpp | 2 +- es-app/src/SystemData.cpp | 4 +- es-app/src/guis/GuiMenu.cpp | 2 +- es-core/src/ThemeData.cpp | 402 ++++++++++++------------ es-core/src/ThemeData.h | 93 +++--- 5 files changed, 250 insertions(+), 253 deletions(-) diff --git a/es-app/src/CollectionSystemsManager.cpp b/es-app/src/CollectionSystemsManager.cpp index bf5c71960..428ab9946 100644 --- a/es-app/src/CollectionSystemsManager.cpp +++ b/es-app/src/CollectionSystemsManager.cpp @@ -1355,7 +1355,7 @@ std::vector CollectionSystemsManager::getSystemsFromTheme() if (themeSets.empty()) return systems; // No theme sets available. - std::map::const_iterator set = + std::map::const_iterator set = themeSets.find(Settings::getInstance()->getString("ThemeSet")); if (set == themeSets.cend()) { // Currently selected theme set is missing, so just pick the first available set. diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index b6f45cbb3..a8cae8c83 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1235,7 +1235,9 @@ void SystemData::loadTheme() // No theme available for this platform. if (!mIsCustomCollectionSystem) { LOG(LogWarning) << "There is no \"" << mThemeFolder - << "\" theme configuration available, system will be unthemed"; + << "\" configuration available for the selected theme set \"" + << Settings::getInstance()->getString("ThemeSet") + << "\", system will be unthemed"; } return; } diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 3d83700b6..ece80132b 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -172,7 +172,7 @@ void GuiMenu::openUIOptions() // Theme selection. auto themeSets = ThemeData::getThemeSets(); if (!themeSets.empty()) { - std::map::const_iterator selectedSet = + std::map::const_iterator selectedSet = themeSets.find(Settings::getInstance()->getString("ThemeSet")); if (selectedSet == themeSets.cend()) selectedSet = themeSets.cbegin(); diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 5a063d315..4ec61aaa8 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -236,54 +236,9 @@ std::map> {"zIndex", FLOAT}, {"legacyZIndexMode", STRING}}}}; -// Helper. -unsigned int getHexColor(const std::string& str) -{ - ThemeException error; - - if (str == "") - throw error << "Empty color property"; - - size_t len {str.size()}; - if (len != 6 && len != 8) - throw error << "Invalid color property \"" << str - << "\" (must be 6 or 8 characters in length)"; - - unsigned int val; - std::stringstream ss; - ss << str; - ss >> std::hex >> val; - - if (len == 6) - val = (val << 8) | 0xFF; - - return val; -} - -std::map mVariables; - -std::string resolvePlaceholders(const std::string& in) -{ - if (in.empty()) - return in; - - const size_t variableBegin {in.find("${")}; - const size_t variableEnd {in.find("}", variableBegin)}; - - if ((variableBegin == std::string::npos) || (variableEnd == std::string::npos)) - return in; - - std::string prefix {in.substr(0, variableBegin)}; - std::string replace {in.substr(variableBegin + 2, variableEnd - (variableBegin + 2))}; - std::string suffix {resolvePlaceholders(in.substr(variableEnd + 1).c_str())}; - - return prefix + mVariables[replace] + suffix; -} - ThemeData::ThemeData() + : mVersion {0} // The version will be loaded from the theme set. { - // The version will be loaded from the theme file. - mVersion = 0; } void ThemeData::loadFile(const std::map& sysDataMap, @@ -333,6 +288,203 @@ void ThemeData::loadFile(const std::map& sysDataMap, parseFeatures(root); } +bool ThemeData::hasView(const std::string& view) +{ + auto viewIt = mViews.find(view); + return (viewIt != mViews.cend()); +} + +std::vector ThemeData::makeExtras(const std::shared_ptr& theme, + const std::string& view) +{ + std::vector comps; + + auto viewIt = theme->mViews.find(view); + if (viewIt == theme->mViews.cend()) + return comps; + + for (auto it = viewIt->second.orderedKeys.cbegin(); // Line break. + it != viewIt->second.orderedKeys.cend(); ++it) { + ThemeElement& elem {viewIt->second.elements.at(*it)}; + if (elem.extra) { + GuiComponent* comp {nullptr}; + const std::string& t {elem.type}; + if (t == "image") + comp = new ImageComponent; + else if (t == "text") + comp = new TextComponent; + else if (t == "animation") + comp = new LottieComponent; + + if (comp) { + comp->setDefaultZIndex(10.0f); + comp->applyTheme(theme, view, *it, ThemeFlags::ALL); + comps.push_back(comp); + } + } + } + + return comps; +} + +const ThemeData::ThemeElement* ThemeData::getElement(const std::string& view, + const std::string& element, + const std::string& expectedType) const +{ + auto viewIt = mViews.find(view); + if (viewIt == mViews.cend()) + return nullptr; // Not found. + + auto elemIt = viewIt->second.elements.find(element); + if (elemIt == viewIt->second.elements.cend()) + return nullptr; + + if (elemIt->second.type != expectedType && !expectedType.empty()) { + LOG(LogWarning) << " requested mismatched theme type for [" << view << "." << element + << "] - expected \"" << expectedType << "\", got \"" << elemIt->second.type + << "\""; + return nullptr; + } + + return &elemIt->second; +} + +std::map ThemeData::getThemeSets() +{ + std::map sets; + + // Check for themes first under the home directory, then under the data installation + // directory (Unix only) and last under the ES-DE binary directory. + +#if defined(__unix__) || defined(__APPLE__) + static const size_t pathCount = 3; +#else + static const size_t pathCount = 2; +#endif + std::string paths[pathCount] = { + Utils::FileSystem::getExePath() + "/themes", +#if defined(__APPLE__) + Utils::FileSystem::getExePath() + "/../Resources/themes", +#elif defined(__unix__) + Utils::FileSystem::getProgramDataPath() + "/themes", +#endif + Utils::FileSystem::getHomePath() + "/.emulationstation/themes" + }; + + for (size_t i = 0; i < pathCount; ++i) { + if (!Utils::FileSystem::isDirectory(paths[i])) + continue; + + Utils::FileSystem::StringList dirContent {Utils::FileSystem::getDirContent(paths[i])}; + + for (Utils::FileSystem::StringList::const_iterator it = dirContent.cbegin(); + it != dirContent.cend(); ++it) { + if (Utils::FileSystem::isDirectory(*it)) { + ThemeSet set = {*it}; + sets[set.getName()] = set; + } + } + } + + return sets; +} + +std::string ThemeData::getThemeFromCurrentSet(const std::string& system) +{ + std::map themeSets {ThemeData::getThemeSets()}; + if (themeSets.empty()) + // No theme sets available. + return ""; + + std::map::const_iterator set = + themeSets.find(Settings::getInstance()->getString("ThemeSet")); + if (set == themeSets.cend()) { + // Currently configured theme set is missing, attempt to load the default theme set + // rbsimple-DE instead, and if that's also missing then pick the first available set. + bool defaultSetFound {true}; + + set = themeSets.find("rbsimple-DE"); + + if (set == themeSets.cend()) { + set = themeSets.cbegin(); + defaultSetFound = false; + } + + LOG(LogWarning) << "Configured theme set \"" + << Settings::getInstance()->getString("ThemeSet") + << "\" does not exist, loading" << (defaultSetFound ? " default " : " ") + << "theme set \"" << set->first << "\" instead"; + + Settings::getInstance()->setString("ThemeSet", set->first); + } + + return set->second.getThemePath(system); +} + +const std::shared_ptr ThemeData::getDefault() +{ + static std::shared_ptr theme = nullptr; + if (theme == nullptr) { + theme = std::shared_ptr(new ThemeData()); + + const std::string path {Utils::FileSystem::getHomePath() + + "/.emulationstation/es_theme_default.xml"}; + if (Utils::FileSystem::exists(path)) { + try { + std::map emptyMap; + theme->loadFile(emptyMap, path); + } + catch (ThemeException& e) { + LOG(LogError) << e.what(); + theme = std::shared_ptr(new ThemeData()); // Reset to empty. + } + } + } + + return theme; +} + +unsigned int ThemeData::getHexColor(const std::string& str) +{ + ThemeException error; + + if (str == "") + throw error << "Empty color property"; + + size_t len {str.size()}; + if (len != 6 && len != 8) + throw error << "Invalid color property \"" << str + << "\" (must be 6 or 8 characters in length)"; + + unsigned int val; + std::stringstream ss; + ss << str; + ss >> std::hex >> val; + + if (len == 6) + val = (val << 8) | 0xFF; + + return val; +} + +std::string ThemeData::resolvePlaceholders(const std::string& in) +{ + if (in.empty()) + return in; + + const size_t variableBegin {in.find("${")}; + const size_t variableEnd {in.find("}", variableBegin)}; + + if ((variableBegin == std::string::npos) || (variableEnd == std::string::npos)) + return in; + + std::string prefix {in.substr(0, variableBegin)}; + std::string replace {in.substr(variableBegin + 2, variableEnd - (variableBegin + 2))}; + std::string suffix {resolvePlaceholders(in.substr(variableEnd + 1).c_str())}; + + return prefix + mVariables[replace] + suffix; +} + void ThemeData::parseIncludes(const pugi::xml_node& root) { ThemeException error; @@ -539,7 +691,7 @@ void ThemeData::parseElement(const pugi::xml_node& root, std::string path = Utils::FileSystem::resolveRelativePath(str, mPaths.back(), true); if (!ResourceManager::getInstance().fileExists(path)) { std::stringstream ss; - LOG(LogWarning) << error.msg << ":"; + LOG(LogWarning) << error.message << ":"; LOG(LogWarning) << "Couldn't find file \"" << node.text().get() << "\" " << ((node.text().get() != path) ? "which resolves to \"" + path + "\"" : @@ -607,159 +759,3 @@ void ThemeData::parseElement(const pugi::xml_node& root, } } } - -bool ThemeData::hasView(const std::string& view) -{ - auto viewIt = mViews.find(view); - return (viewIt != mViews.cend()); -} - -const ThemeData::ThemeElement* ThemeData::getElement(const std::string& view, - const std::string& element, - const std::string& expectedType) const -{ - auto viewIt = mViews.find(view); - if (viewIt == mViews.cend()) - return nullptr; // Not found. - - auto elemIt = viewIt->second.elements.find(element); - if (elemIt == viewIt->second.elements.cend()) - return nullptr; - - if (elemIt->second.type != expectedType && !expectedType.empty()) { - LOG(LogWarning) << " requested mismatched theme type for [" << view << "." << element - << "] - expected \"" << expectedType << "\", got \"" << elemIt->second.type - << "\""; - return nullptr; - } - - return &elemIt->second; -} - -const std::shared_ptr ThemeData::getDefault() -{ - static std::shared_ptr theme = nullptr; - if (theme == nullptr) { - theme = std::shared_ptr(new ThemeData()); - - const std::string path {Utils::FileSystem::getHomePath() + - "/.emulationstation/es_theme_default.xml"}; - if (Utils::FileSystem::exists(path)) { - try { - std::map emptyMap; - theme->loadFile(emptyMap, path); - } - catch (ThemeException& e) { - LOG(LogError) << e.what(); - theme = std::shared_ptr(new ThemeData()); // Reset to empty. - } - } - } - - return theme; -} - -std::vector ThemeData::makeExtras(const std::shared_ptr& theme, - const std::string& view) -{ - std::vector comps; - - auto viewIt = theme->mViews.find(view); - if (viewIt == theme->mViews.cend()) - return comps; - - for (auto it = viewIt->second.orderedKeys.cbegin(); // Line break. - it != viewIt->second.orderedKeys.cend(); ++it) { - ThemeElement& elem {viewIt->second.elements.at(*it)}; - if (elem.extra) { - GuiComponent* comp {nullptr}; - const std::string& t {elem.type}; - if (t == "image") - comp = new ImageComponent; - else if (t == "text") - comp = new TextComponent; - else if (t == "animation") - comp = new LottieComponent; - - if (comp) { - comp->setDefaultZIndex(10.0f); - comp->applyTheme(theme, view, *it, ThemeFlags::ALL); - comps.push_back(comp); - } - } - } - - return comps; -} - -std::map ThemeData::getThemeSets() -{ - std::map sets; - - // Check for themes first under the home directory, then under the data installation - // directory (Unix only) and last under the ES-DE binary directory. - -#if defined(__unix__) || defined(__APPLE__) - static const size_t pathCount = 3; -#else - static const size_t pathCount = 2; -#endif - std::string paths[pathCount] = { - Utils::FileSystem::getExePath() + "/themes", -#if defined(__APPLE__) - Utils::FileSystem::getExePath() + "/../Resources/themes", -#elif defined(__unix__) - Utils::FileSystem::getProgramDataPath() + "/themes", -#endif - Utils::FileSystem::getHomePath() + "/.emulationstation/themes" - }; - - for (size_t i = 0; i < pathCount; ++i) { - if (!Utils::FileSystem::isDirectory(paths[i])) - continue; - - Utils::FileSystem::StringList dirContent {Utils::FileSystem::getDirContent(paths[i])}; - - for (Utils::FileSystem::StringList::const_iterator it = dirContent.cbegin(); - it != dirContent.cend(); ++it) { - if (Utils::FileSystem::isDirectory(*it)) { - ThemeSet set = {*it}; - sets[set.getName()] = set; - } - } - } - - return sets; -} - -std::string ThemeData::getThemeFromCurrentSet(const std::string& system) -{ - std::map themeSets {ThemeData::getThemeSets()}; - if (themeSets.empty()) - // No theme sets available. - return ""; - - std::map::const_iterator set = - themeSets.find(Settings::getInstance()->getString("ThemeSet")); - if (set == themeSets.cend()) { - // Currently configured theme set is missing, attempt to load the default theme set - // rbsimple-DE instead, and if that's also missing then pick the first available set. - bool defaultSetFound {true}; - - set = themeSets.find("rbsimple-DE"); - - if (set == themeSets.cend()) { - set = themeSets.cbegin(); - defaultSetFound = false; - } - - LOG(LogWarning) << "Configured theme set \"" - << Settings::getInstance()->getString("ThemeSet") - << "\" does not exist, loading" << (defaultSetFound ? " default " : " ") - << "theme set \"" << set->first << "\" instead"; - - Settings::getInstance()->setString("ThemeSet", set->first); - } - - return set->second.getThemePath(system); -} diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 3f145d794..b55315ec1 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -26,8 +26,6 @@ namespace pugi class xml_node; } -template class TextListComponent; - class GuiComponent; class ImageComponent; class NinePatchComponent; @@ -65,38 +63,28 @@ namespace ThemeFlags class ThemeException : public std::exception { public: - std::string msg; + std::string message; - const char* what() const throw() { return msg.c_str(); } + const char* what() const throw() { return message.c_str(); } - template friend ThemeException& operator<<(ThemeException& e, T msg); + template friend ThemeException& operator<<(ThemeException& e, T message) + { + std::stringstream ss; + ss << e.message << message; + e.message = ss.str(); + return e; + } void setFiles(const std::deque& deque) { + // Add all paths to the error message, separated by -> so it's easy to read the log + // output in case of theme loading errors. *this << "\"" << deque.front() << "\""; for (auto it = deque.cbegin() + 1; it != deque.cend(); ++it) *this << " -> \"" << (*it) << "\""; } }; -template ThemeException& operator<<(ThemeException& e, T appendMsg) -{ - std::stringstream ss; - ss << e.msg << appendMsg; - e.msg = ss.str(); - return e; -} - -struct ThemeSet { - std::string path; - - std::string getName() const { return Utils::FileSystem::getStem(path); } - std::string getThemePath(const std::string& system) const - { - return path + "/" + system + "/theme.xml"; - } -}; - class ThemeData { public: @@ -168,19 +156,32 @@ public: } }; -private: - class ThemeView - { - public: - std::map elements; - std::vector orderedKeys; - }; - -public: ThemeData(); + struct ThemeSet { + std::string path; + + std::string getName() const { return Utils::FileSystem::getStem(path); } + std::string getThemePath(const std::string& system) const + { + return path + "/" + system + "/theme.xml"; + } + }; + // Throws ThemeException. void loadFile(const std::map& sysDataMap, const std::string& path); + bool hasView(const std::string& view); + + static std::vector makeExtras(const std::shared_ptr& theme, + const std::string& view); + + // If expectedType is an empty string, then do no type checking. + const ThemeElement* getElement(const std::string& view, + const std::string& element, + const std::string& expectedType) const; + + static std::map getThemeSets(); + static std::string getThemeFromCurrentSet(const std::string& system); enum ElementPropertyType { NORMALIZED_RECT, @@ -192,22 +193,16 @@ public: BOOLEAN }; - bool hasView(const std::string& view); - - // If expectedType is an empty string, will do no type checking. - const ThemeElement* getElement(const std::string& view, - const std::string& element, - const std::string& expectedType) const; - - static std::vector makeExtras(const std::shared_ptr& theme, - const std::string& view); - - static const std::shared_ptr getDefault(); - - static std::map getThemeSets(); - static std::string getThemeFromCurrentSet(const std::string& system); + std::map mVariables; private: + class ThemeView + { + public: + std::map elements; + std::vector orderedKeys; + }; + static std::map> sElementMap; static std::vector sSupportedFeatures; static std::vector sSupportedViews; @@ -215,8 +210,12 @@ private: std::deque mPaths; float mVersion; - void parseFeatures(const pugi::xml_node& themeRoot); + static const std::shared_ptr getDefault(); + unsigned int getHexColor(const std::string& str); + std::string resolvePlaceholders(const std::string& in); + void parseIncludes(const pugi::xml_node& themeRoot); + void parseFeatures(const pugi::xml_node& themeRoot); void parseVariables(const pugi::xml_node& root); void parseViews(const pugi::xml_node& themeRoot); void parseView(const pugi::xml_node& viewNode, ThemeView& view);