diff --git a/src/ThemeData.cpp b/src/ThemeData.cpp index 89169779d..f8a4d9b9f 100644 --- a/src/ThemeData.cpp +++ b/src/ThemeData.cpp @@ -39,81 +39,7 @@ namespace fs = boost::filesystem; #define MINIMUM_THEME_VERSION 3 #define CURRENT_THEME_VERSION 3 -// still TODO: -// * how to do ? - -ThemeData::ThemeData() -{ - mVersion = 0; -} - -void ThemeData::loadFile(const std::string& path) -{ - ThemeException error; - error.setFile(path); - - mPath = path; - - if(!fs::exists(path)) - throw error << "Missing file!"; - - mVersion = 0; - mViews.clear(); - - pugi::xml_document doc; - pugi::xml_parse_result res = doc.load_file(path.c_str()); - if(!res) - throw error << "XML parsing error: \n " << res.description(); - - pugi::xml_node root = doc.child("theme"); - if(!root) - throw error << "Missing tag!"; - - // parse version - mVersion = root.child("version").text().as_float(-404); - if(mVersion == -404) - throw error << " tag missing!\n It's either out of date or you need to add " << CURRENT_THEME_VERSION << " inside your tag."; - - - if(mVersion < MINIMUM_THEME_VERSION) - throw error << "Theme is version " << mVersion << ". Minimum supported version is " << MINIMUM_THEME_VERSION << "."; - - // parse views - for(pugi::xml_node node = root.child("view"); node; node = node.next_sibling("view")) - { - if(!node.attribute("name")) - throw error << "View missing \"name\" attribute!"; - - ThemeView view = parseView(node); - - if(view.elements.size() > 0) - mViews[node.attribute("name").as_string()] = view; - } -} - -ThemeData::ThemeView ThemeData::parseView(const pugi::xml_node& root) -{ - ThemeView view; - ThemeException error; - error.setFile(mPath.string()); - - for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()) - { - if(!node.attribute("name")) - throw error << "Element of type \"" << node.name() << "\" missing \"name\" attribute!"; - - auto elemTypeIt = sElementMap.find(node.name()); - if(elemTypeIt == sElementMap.end()) - throw error << "Unknown element of type \"" << node.name() << "\"!"; - - ThemeElement element = parseElement(node, elemTypeIt->second); - - view.elements[node.attribute("name").as_string()] = element; - } - - return view; -} - +// helper unsigned int getHexColor(const char* str) { ThemeException error; @@ -135,6 +61,7 @@ unsigned int getHexColor(const char* str) return val; } +// helper std::string resolvePath(const char* in, const fs::path& relative) { if(!in || in[0] == '\0') @@ -158,12 +85,130 @@ std::string resolvePath(const char* in, const fs::path& relative) } -ThemeData::ThemeElement ThemeData::parseElement(const pugi::xml_node& root, const std::map& typeMap) + +ThemeData::ThemeData() +{ + mVersion = 0; +} + +void ThemeData::loadFile(const std::string& path) +{ + mPaths.push_back(path); + + ThemeException error; + error.setFiles(mPaths); + + if(!fs::exists(path)) + throw error << "File does not exist!"; + + mVersion = 0; + mViews.clear(); + + pugi::xml_document doc; + pugi::xml_parse_result res = doc.load_file(path.c_str()); + if(!res) + throw error << "XML parsing error: \n " << res.description(); + + pugi::xml_node root = doc.child("theme"); + if(!root) + throw error << "Missing tag!"; + + // parse version + mVersion = root.child("version").text().as_float(-404); + if(mVersion == -404) + throw error << " tag missing!\n It's either out of date or you need to add " << CURRENT_THEME_VERSION << " inside your tag."; + + if(mVersion < MINIMUM_THEME_VERSION) + throw error << "Theme is version " << mVersion << ". Minimum supported version is " << MINIMUM_THEME_VERSION << "."; + + parseIncludes(root); + parseViews(root); +} + + +void ThemeData::parseIncludes(const pugi::xml_node& root) { ThemeException error; - error.setFile(mPath.string()); + error.setFiles(mPaths); + + for(pugi::xml_node node = root.child("include"); node; node = node.next_sibling("include")) + { + const char* relPath = node.text().get(); + std::string path = resolvePath(relPath, mPaths.back()); + if(!ResourceManager::getInstance()->fileExists(path)) + throw error << "Included file \"" << relPath << "\" not found! (resolved to \"" << path << "\")"; + + error << " from included file \"" << relPath << "\":\n "; + + mPaths.push_back(path); + + pugi::xml_document includeDoc; + pugi::xml_parse_result result = includeDoc.load_file(path.c_str()); + if(!result) + throw error << "Error parsing file: \n " << result.description(); + + pugi::xml_node root = includeDoc.child("theme"); + if(!root) + throw error << "Missing tag!"; + + parseIncludes(root); + parseViews(root); + + mPaths.pop_back(); + } +} + +void ThemeData::parseViews(const pugi::xml_node& root) +{ + ThemeException error; + error.setFiles(mPaths); + + pugi::xml_node common = root.find_child_by_attribute("view", "name", "common"); + + // parse views + for(pugi::xml_node node = root.child("view"); node; node = node.next_sibling("view")) + { + if(!node.attribute("name")) + throw error << "View missing \"name\" attribute!"; + + const char* viewKey = node.attribute("name").as_string(); + ThemeView& view = mViews.insert(std::make_pair(viewKey, ThemeView())).first->second; + + // load common first + if(common && node != common) + parseView(common, view); + + parseView(node, view); + } +} + +void ThemeData::parseView(const pugi::xml_node& root, ThemeView& view) +{ + ThemeException error; + error.setFiles(mPaths); + + for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()) + { + if(!node.attribute("name")) + throw error << "Element of type \"" << node.name() << "\" missing \"name\" attribute!"; + + auto elemTypeIt = sElementMap.find(node.name()); + if(elemTypeIt == sElementMap.end()) + throw error << "Unknown element of type \"" << node.name() << "\"!"; + + const char* elemKey = node.attribute("name").as_string(); + + parseElement(node, elemTypeIt->second, + view.elements.insert(std::make_pair(elemKey, ThemeElement())).first->second); + } +} + + +void ThemeData::parseElement(const pugi::xml_node& root, const std::map& typeMap, ThemeElement& element) +{ + ThemeException error; + error.setFiles(mPaths); - ThemeElement element; element.extra = root.attribute("extra").as_bool(false); for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()) @@ -195,9 +240,16 @@ ThemeData::ThemeElement ThemeData::parseElement(const pugi::xml_node& root, cons break; case PATH: { - std::string path = resolvePath(node.text().as_string(), mPath.string()); - if(!fs::exists(path)) - LOG(LogWarning) << " Warning: theme \"" << mPath << "\" - could not find file \"" << node.text().get() << "\" (resolved to \"" << path << "\")"; + std::string path = resolvePath(node.text().as_string(), mPaths.back().string()); + if(!ResourceManager::getInstance()->fileExists(path)) + { + std::stringstream ss; + ss << " Warning " << error.msg; // "from theme yadda yadda, included file yadda yadda + ss << "could not find file \"" << node.text().get() << "\" "; + if(node.text().get() != path) + ss << "(which resolved to \"" << path << "\") "; + LOG(LogWarning) << ss.str(); + } element.properties[node.name()] = path; break; } @@ -214,6 +266,10 @@ ThemeData::ThemeElement ThemeData::parseElement(const pugi::xml_node& root, cons throw error << "Unknown ElementPropertyType for " << root.attribute("name").as_string() << " property " << node.name(); } } - - return element; } + +ThemeData::ThemeView::~ThemeView() +{ + for(auto it = mExtras.begin(); it != mExtras.end(); it++) + delete *it; +} \ No newline at end of file diff --git a/src/ThemeData.h b/src/ThemeData.h index cf2cfb897..d1eccc01b 100644 --- a/src/ThemeData.h +++ b/src/ThemeData.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -40,16 +41,21 @@ namespace ThemeFlags class ThemeException : public std::exception { -protected: +public: std::string msg; -public: virtual const char* what() const throw() { return msg.c_str(); } template friend ThemeException& operator<<(ThemeException& e, T msg); - inline void setFile(const std::string& filename) { *this << "Error loading theme from \"" << filename << "\":\n "; } + inline void setFiles(const std::deque& deque) + { + *this << "from theme \"" << deque.front().string() << "\"\n"; + for(auto it = deque.begin() + 1; it != deque.end(); it++) + *this << " (from included file \"" << (*it).string() << "\")\n"; + *this << " "; + } }; template @@ -81,17 +87,17 @@ private: class ThemeView { + private: + bool mExtrasDirty; + std::vector mExtras; + public: ThemeView() : mExtrasDirty(true) {} - virtual ~ThemeView() { for(auto it = mExtras.begin(); it != mExtras.end(); it++) delete *it; } + virtual ~ThemeView(); std::map elements; const std::vector& getExtras(Window* window); - - private: - bool mExtrasDirty; - std::vector mExtras; }; public: @@ -122,14 +128,19 @@ public: void playSound(const std::string& name); +private: + void applyPosAndSize(ThemeElement* elem, GuiComponent* comp, unsigned int properties); + private: static std::map< std::string, std::map > sElementMap; - boost::filesystem::path mPath; + std::deque mPaths; float mVersion; - ThemeView parseView(const pugi::xml_node& view); - ThemeElement parseElement(const pugi::xml_node& element, const std::map& typeMap); + void parseIncludes(const pugi::xml_node& themeRoot); + void parseViews(const pugi::xml_node& themeRoot); + void parseView(const pugi::xml_node& viewNode, ThemeView& view); + void parseElement(const pugi::xml_node& elementNode, const std::map& typeMap, ThemeElement& element); ThemeElement* getElement(const std::string& viewName, const std::string& elementName); diff --git a/src/ThemeData_applicators.cpp b/src/ThemeData_applicators.cpp index 83e4d6aa6..1e2b6c17f 100644 --- a/src/ThemeData_applicators.cpp +++ b/src/ThemeData_applicators.cpp @@ -9,6 +9,7 @@ using namespace ThemeFlags; + Eigen::Vector2f getScale(GuiComponent* comp) { if(comp && comp->getParent()) @@ -17,6 +18,20 @@ Eigen::Vector2f getScale(GuiComponent* comp) return Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); } +void ThemeData::applyPosAndSize(ThemeElement* elem, GuiComponent* comp, unsigned int properties) +{ + Eigen::Vector2f scale = getScale(comp); + + if(properties & POSITION && elem->has("pos")) + { + Eigen::Vector2f denormalized = elem->get("pos").cwiseProduct(scale); + comp->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0)); + } + + if(properties & ThemeFlags::SIZE && elem->has("size")) + comp->setSize(elem->get("size").cwiseProduct(scale)); +} + ThemeData::ThemeElement* ThemeData::getElement(const std::string& viewName, const std::string& elementName) { auto viewIt = mViews.find(viewName); @@ -76,11 +91,7 @@ void ThemeData::applyToNinePatch(const std::string& viewName, const std::string& patch->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0)); } - if(properties & PATH && elem->has("path")) - patch->setImagePath(elem->get("path")); - - if(properties & ThemeFlags::SIZE && elem->has("size")) - patch->setSize(elem->get("size").cwiseProduct(scale)); + applyPosAndSize(elem, patch, properties); } void ThemeData::applyToText(const std::string& viewName, const std::string& elementName, TextComponent* text, unsigned int properties) diff --git a/src/views/gamelist/ISimpleGameListView.cpp b/src/views/gamelist/ISimpleGameListView.cpp index fb678d1ee..082ac118b 100644 --- a/src/views/gamelist/ISimpleGameListView.cpp +++ b/src/views/gamelist/ISimpleGameListView.cpp @@ -24,8 +24,8 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame void ISimpleGameListView::onThemeChanged(const std::shared_ptr& theme) { using namespace ThemeFlags; - theme->applyToImage("common", "background", &mBackground, PATH | TILING); - theme->applyToImage("common", "header", &mHeaderImage, PATH); + theme->applyToImage(getName(), "background", &mBackground, PATH | TILING); + theme->applyToImage(getName(), "header", &mHeaderImage, PATH); if(mHeaderImage.hasImage()) {