#pragma once #include #include #include #include #include #include #include #include #include #include "pugiXML/pugixml.hpp" #include "GuiComponent.h" template class TextListComponent; class Sound; class ImageComponent; class NinePatchComponent; class TextComponent; class Window; namespace ThemeFlags { enum PropertyFlags : unsigned int { PATH = 1, POSITION = 2, SIZE = 4, ORIGIN = 8, COLOR = 16, FONT_PATH = 32, FONT_SIZE = 64, TILING = 128, SOUND = 256, CENTER = 512, TEXT = 1024, ALL = 0xFFFFFFFF }; } class ThemeException : public std::exception { public: std::string msg; virtual const char* what() const throw() { return msg.c_str(); } template friend ThemeException& operator<<(ThemeException& e, T msg); 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 ThemeException& operator<<(ThemeException& e, T appendMsg) { std::stringstream ss; ss << e.msg << appendMsg; e.msg = ss.str(); return e; } class ThemeExtras : public GuiComponent { public: ThemeExtras(Window* window) : GuiComponent(window) {}; // will take ownership of the components within extras (delete them in destructor or when setExtras is called again) void setExtras(const std::vector& extras); virtual ~ThemeExtras(); private: std::vector mExtras; }; class ThemeData { public: class ThemeElement { public: bool extra; std::string type; std::map< std::string, boost::variant > properties; template T get(const std::string& prop) const { return boost::get(properties.at(prop)); } inline bool has(const std::string& prop) const { return (properties.find(prop) != properties.end()); } }; private: class ThemeView { public: std::map elements; }; public: ThemeData(); // throws ThemeException void loadFile(const std::string& path); enum ElementPropertyType { NORMALIZED_PAIR, PATH, STRING, COLOR, FLOAT, BOOLEAN }; void renderExtras(const std::string& view, Window* window, const Eigen::Affine3f& transform); // 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, Window* window); static const std::shared_ptr& getDefault(); private: static std::map< std::string, std::map > sElementMap; std::deque mPaths; float mVersion; 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); std::map mViews; }; // okay ideas for applying themes to GuiComponents: // 1. ThemeData::applyToImage(component, args) // NO, BECAUSE: // - for templated types (TextListComponent) have to include the whole template in a header // - inconsistent definitions if mixing templated types (some in a .cpp, some in a .h/.inl) // 2. template ThemeData::apply(component, args) with specialized templates // NO, BECAUSE: // - doesn't solve the first drawback // - can't customize arguments for specific types // 3. GuiComponent::applyTheme(theme, args) - WINNER // NO, BECAUSE: // - can't access private members of ThemeData // - can't this be solved with enough getters? // - theme->hasElement and theme->getProperty will require 2x as many map lookups (4 vs 2) // - why not just return a const ThemeElement...