Better "common" view.

Added <include> tag.
This commit is contained in:
Aloshi 2013-12-30 21:48:28 -06:00
parent 7f46e50688
commit 8bc33ce309
4 changed files with 179 additions and 101 deletions

View file

@ -39,81 +39,7 @@ namespace fs = boost::filesystem;
#define MINIMUM_THEME_VERSION 3 #define MINIMUM_THEME_VERSION 3
#define CURRENT_THEME_VERSION 3 #define CURRENT_THEME_VERSION 3
// still TODO: // helper
// * how to do <include>?
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 <theme> tag!";
// parse version
mVersion = root.child("version").text().as_float(-404);
if(mVersion == -404)
throw error << "<version> tag missing!\n It's either out of date or you need to add <version>" << CURRENT_THEME_VERSION << "</version> inside your <theme> 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;
}
unsigned int getHexColor(const char* str) unsigned int getHexColor(const char* str)
{ {
ThemeException error; ThemeException error;
@ -135,6 +61,7 @@ unsigned int getHexColor(const char* str)
return val; return val;
} }
// helper
std::string resolvePath(const char* in, const fs::path& relative) std::string resolvePath(const char* in, const fs::path& relative)
{ {
if(!in || in[0] == '\0') 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<std::string, ElementPropertyType>& 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 <theme> tag!";
// parse version
mVersion = root.child("version").text().as_float(-404);
if(mVersion == -404)
throw error << "<version> tag missing!\n It's either out of date or you need to add <version>" << CURRENT_THEME_VERSION << "</version> inside your <theme> 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; 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 <theme> 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<std::string, ThemeView>(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<std::string, ThemeElement>(elemKey, ThemeElement())).first->second);
}
}
void ThemeData::parseElement(const pugi::xml_node& root, const std::map<std::string, ElementPropertyType>& typeMap, ThemeElement& element)
{
ThemeException error;
error.setFiles(mPaths);
ThemeElement element;
element.extra = root.attribute("extra").as_bool(false); element.extra = root.attribute("extra").as_bool(false);
for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()) 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; break;
case PATH: case PATH:
{ {
std::string path = resolvePath(node.text().as_string(), mPath.string()); std::string path = resolvePath(node.text().as_string(), mPaths.back().string());
if(!fs::exists(path)) if(!ResourceManager::getInstance()->fileExists(path))
LOG(LogWarning) << " Warning: theme \"" << mPath << "\" - could not find file \"" << node.text().get() << "\" (resolved to \"" << 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; element.properties[node.name()] = path;
break; 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(); 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;
}

View file

@ -4,6 +4,7 @@
#include <sstream> #include <sstream>
#include <memory> #include <memory>
#include <map> #include <map>
#include <deque>
#include <string> #include <string>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/variant.hpp> #include <boost/variant.hpp>
@ -40,16 +41,21 @@ namespace ThemeFlags
class ThemeException : public std::exception class ThemeException : public std::exception
{ {
protected: public:
std::string msg; std::string msg;
public:
virtual const char* what() const throw() { return msg.c_str(); } virtual const char* what() const throw() { return msg.c_str(); }
template<typename T> template<typename T>
friend ThemeException& operator<<(ThemeException& e, T msg); 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<boost::filesystem::path>& 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<typename T> template<typename T>
@ -81,17 +87,17 @@ private:
class ThemeView class ThemeView
{ {
private:
bool mExtrasDirty;
std::vector<GuiComponent*> mExtras;
public: public:
ThemeView() : mExtrasDirty(true) {} ThemeView() : mExtrasDirty(true) {}
virtual ~ThemeView() { for(auto it = mExtras.begin(); it != mExtras.end(); it++) delete *it; } virtual ~ThemeView();
std::map<std::string, ThemeElement> elements; std::map<std::string, ThemeElement> elements;
const std::vector<GuiComponent*>& getExtras(Window* window); const std::vector<GuiComponent*>& getExtras(Window* window);
private:
bool mExtrasDirty;
std::vector<GuiComponent*> mExtras;
}; };
public: public:
@ -122,14 +128,19 @@ public:
void playSound(const std::string& name); void playSound(const std::string& name);
private:
void applyPosAndSize(ThemeElement* elem, GuiComponent* comp, unsigned int properties);
private: private:
static std::map< std::string, std::map<std::string, ElementPropertyType> > sElementMap; static std::map< std::string, std::map<std::string, ElementPropertyType> > sElementMap;
boost::filesystem::path mPath; std::deque<boost::filesystem::path> mPaths;
float mVersion; float mVersion;
ThemeView parseView(const pugi::xml_node& view); void parseIncludes(const pugi::xml_node& themeRoot);
ThemeElement parseElement(const pugi::xml_node& element, const std::map<std::string, ElementPropertyType>& typeMap); 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<std::string, ElementPropertyType>& typeMap, ThemeElement& element);
ThemeElement* getElement(const std::string& viewName, const std::string& elementName); ThemeElement* getElement(const std::string& viewName, const std::string& elementName);

View file

@ -9,6 +9,7 @@
using namespace ThemeFlags; using namespace ThemeFlags;
Eigen::Vector2f getScale(GuiComponent* comp) Eigen::Vector2f getScale(GuiComponent* comp)
{ {
if(comp && comp->getParent()) if(comp && comp->getParent())
@ -17,6 +18,20 @@ Eigen::Vector2f getScale(GuiComponent* comp)
return Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); 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<Eigen::Vector2f>("pos").cwiseProduct(scale);
comp->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE && elem->has("size"))
comp->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
}
ThemeData::ThemeElement* ThemeData::getElement(const std::string& viewName, const std::string& elementName) ThemeData::ThemeElement* ThemeData::getElement(const std::string& viewName, const std::string& elementName)
{ {
auto viewIt = mViews.find(viewName); 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)); patch->setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
} }
if(properties & PATH && elem->has("path")) applyPosAndSize(elem, patch, properties);
patch->setImagePath(elem->get<std::string>("path"));
if(properties & ThemeFlags::SIZE && elem->has("size"))
patch->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
} }
void ThemeData::applyToText(const std::string& viewName, const std::string& elementName, TextComponent* text, unsigned int properties) void ThemeData::applyToText(const std::string& viewName, const std::string& elementName, TextComponent* text, unsigned int properties)

View file

@ -24,8 +24,8 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame
void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme) void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{ {
using namespace ThemeFlags; using namespace ThemeFlags;
theme->applyToImage("common", "background", &mBackground, PATH | TILING); theme->applyToImage(getName(), "background", &mBackground, PATH | TILING);
theme->applyToImage("common", "header", &mHeaderImage, PATH); theme->applyToImage(getName(), "header", &mHeaderImage, PATH);
if(mHeaderImage.hasImage()) if(mHeaderImage.hasImage())
{ {