Cleaned up ThemeData a bit.

This commit is contained in:
Leon Styhre 2022-01-23 20:03:50 +01:00
parent 644f79ebec
commit dc20a9e21b
5 changed files with 250 additions and 253 deletions

View file

@ -1355,7 +1355,7 @@ std::vector<std::string> CollectionSystemsManager::getSystemsFromTheme()
if (themeSets.empty())
return systems; // No theme sets available.
std::map<std::string, ThemeSet>::const_iterator set =
std::map<std::string, ThemeData::ThemeSet>::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.

View file

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

View file

@ -172,7 +172,7 @@ void GuiMenu::openUIOptions()
// Theme selection.
auto themeSets = ThemeData::getThemeSets();
if (!themeSets.empty()) {
std::map<std::string, ThemeSet>::const_iterator selectedSet =
std::map<std::string, ThemeData::ThemeSet>::const_iterator selectedSet =
themeSets.find(Settings::getInstance()->getString("ThemeSet"));
if (selectedSet == themeSets.cend())
selectedSet = themeSets.cbegin();

View file

@ -236,54 +236,9 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"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<std::string, std::string> 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<std::string, std::string>& sysDataMap,
@ -333,6 +288,203 @@ void ThemeData::loadFile(const std::map<std::string, std::string>& sysDataMap,
parseFeatures(root);
}
bool ThemeData::hasView(const std::string& view)
{
auto viewIt = mViews.find(view);
return (viewIt != mViews.cend());
}
std::vector<GuiComponent*> ThemeData::makeExtras(const std::shared_ptr<ThemeData>& theme,
const std::string& view)
{
std::vector<GuiComponent*> 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<std::string, ThemeData::ThemeSet> ThemeData::getThemeSets()
{
std::map<std::string, ThemeSet> 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<std::string, ThemeSet> themeSets {ThemeData::getThemeSets()};
if (themeSets.empty())
// No theme sets available.
return "";
std::map<std::string, ThemeSet>::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> ThemeData::getDefault()
{
static std::shared_ptr<ThemeData> theme = nullptr;
if (theme == nullptr) {
theme = std::shared_ptr<ThemeData>(new ThemeData());
const std::string path {Utils::FileSystem::getHomePath() +
"/.emulationstation/es_theme_default.xml"};
if (Utils::FileSystem::exists(path)) {
try {
std::map<std::string, std::string> emptyMap;
theme->loadFile(emptyMap, path);
}
catch (ThemeException& e) {
LOG(LogError) << e.what();
theme = std::shared_ptr<ThemeData>(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> ThemeData::getDefault()
{
static std::shared_ptr<ThemeData> theme = nullptr;
if (theme == nullptr) {
theme = std::shared_ptr<ThemeData>(new ThemeData());
const std::string path {Utils::FileSystem::getHomePath() +
"/.emulationstation/es_theme_default.xml"};
if (Utils::FileSystem::exists(path)) {
try {
std::map<std::string, std::string> emptyMap;
theme->loadFile(emptyMap, path);
}
catch (ThemeException& e) {
LOG(LogError) << e.what();
theme = std::shared_ptr<ThemeData>(new ThemeData()); // Reset to empty.
}
}
}
return theme;
}
std::vector<GuiComponent*> ThemeData::makeExtras(const std::shared_ptr<ThemeData>& theme,
const std::string& view)
{
std::vector<GuiComponent*> 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<std::string, ThemeSet> ThemeData::getThemeSets()
{
std::map<std::string, ThemeSet> 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<std::string, ThemeSet> themeSets {ThemeData::getThemeSets()};
if (themeSets.empty())
// No theme sets available.
return "";
std::map<std::string, ThemeSet>::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);
}

View file

@ -26,8 +26,6 @@ namespace pugi
class xml_node;
}
template <typename T> class TextListComponent;
class GuiComponent;
class ImageComponent;
class NinePatchComponent;
@ -65,35 +63,25 @@ 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 <typename T> friend ThemeException& operator<<(ThemeException& e, T msg);
void setFiles(const std::deque<std::string>& deque)
{
*this << "\"" << deque.front() << "\"";
for (auto it = deque.cbegin() + 1; it != deque.cend(); ++it)
*this << " -> \"" << (*it) << "\"";
}
};
template <typename T> ThemeException& operator<<(ThemeException& e, T appendMsg)
template <typename T> friend ThemeException& operator<<(ThemeException& e, T message)
{
std::stringstream ss;
ss << e.msg << appendMsg;
e.msg = ss.str();
ss << e.message << message;
e.message = 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
void setFiles(const std::deque<std::string>& deque)
{
return path + "/" + system + "/theme.xml";
// 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) << "\"";
}
};
@ -168,19 +156,32 @@ public:
}
};
private:
class ThemeView
{
public:
std::map<std::string, ThemeElement> elements;
std::vector<std::string> 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<std::string, std::string>& sysDataMap, const std::string& path);
bool hasView(const std::string& view);
static std::vector<GuiComponent*> makeExtras(const std::shared_ptr<ThemeData>& 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<std::string, ThemeSet> 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<GuiComponent*> makeExtras(const std::shared_ptr<ThemeData>& theme,
const std::string& view);
static const std::shared_ptr<ThemeData> getDefault();
static std::map<std::string, ThemeSet> getThemeSets();
static std::string getThemeFromCurrentSet(const std::string& system);
std::map<std::string, std::string> mVariables;
private:
class ThemeView
{
public:
std::map<std::string, ThemeElement> elements;
std::vector<std::string> orderedKeys;
};
static std::map<std::string, std::map<std::string, ElementPropertyType>> sElementMap;
static std::vector<std::string> sSupportedFeatures;
static std::vector<std::string> sSupportedViews;
@ -215,8 +210,12 @@ private:
std::deque<std::string> mPaths;
float mVersion;
void parseFeatures(const pugi::xml_node& themeRoot);
static const std::shared_ptr<ThemeData> 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);