mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 23:55:38 +00:00
Better "common" view.
Added <include> tag.
This commit is contained in:
parent
7f46e50688
commit
8bc33ce309
|
@ -39,81 +39,7 @@ namespace fs = boost::filesystem;
|
|||
#define MINIMUM_THEME_VERSION 3
|
||||
#define CURRENT_THEME_VERSION 3
|
||||
|
||||
// still TODO:
|
||||
// * 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;
|
||||
}
|
||||
|
||||
// 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<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;
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
|
@ -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<typename T>
|
||||
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>
|
||||
|
@ -81,17 +87,17 @@ private:
|
|||
|
||||
class ThemeView
|
||||
{
|
||||
private:
|
||||
bool mExtrasDirty;
|
||||
std::vector<GuiComponent*> mExtras;
|
||||
|
||||
public:
|
||||
ThemeView() : mExtrasDirty(true) {}
|
||||
virtual ~ThemeView() { for(auto it = mExtras.begin(); it != mExtras.end(); it++) delete *it; }
|
||||
virtual ~ThemeView();
|
||||
|
||||
std::map<std::string, ThemeElement> elements;
|
||||
|
||||
const std::vector<GuiComponent*>& getExtras(Window* window);
|
||||
|
||||
private:
|
||||
bool mExtrasDirty;
|
||||
std::vector<GuiComponent*> 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<std::string, ElementPropertyType> > sElementMap;
|
||||
|
||||
boost::filesystem::path mPath;
|
||||
std::deque<boost::filesystem::path> mPaths;
|
||||
float mVersion;
|
||||
|
||||
ThemeView parseView(const pugi::xml_node& view);
|
||||
ThemeElement parseElement(const pugi::xml_node& element, const std::map<std::string, ElementPropertyType>& 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<std::string, ElementPropertyType>& typeMap, ThemeElement& element);
|
||||
|
||||
ThemeElement* getElement(const std::string& viewName, const std::string& elementName);
|
||||
|
||||
|
|
|
@ -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<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)
|
||||
{
|
||||
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<std::string>("path"));
|
||||
|
||||
if(properties & ThemeFlags::SIZE && elem->has("size"))
|
||||
patch->setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
|
||||
applyPosAndSize(elem, patch, properties);
|
||||
}
|
||||
|
||||
void ThemeData::applyToText(const std::string& viewName, const std::string& elementName, TextComponent* text, unsigned int properties)
|
||||
|
|
|
@ -24,8 +24,8 @@ ISimpleGameListView::ISimpleGameListView(Window* window, FileData* root) : IGame
|
|||
void ISimpleGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& 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())
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue