Load GUI from XML
This commit is contained in:
425
GUILoader.cc
Normal file
425
GUILoader.cc
Normal file
@@ -0,0 +1,425 @@
|
||||
#include <libxml/parser.h>
|
||||
|
||||
#include "GUILoader.hh"
|
||||
#include "FontProvider.hh"
|
||||
#include "ImageProvider.hh"
|
||||
#include "Widget.hh"
|
||||
#include "TextWidget.hh"
|
||||
#include "LinearLayout.hh"
|
||||
|
||||
struct Style {
|
||||
int width{WRAP_CONTENT}, height{WRAP_CONTENT};
|
||||
SDL_Surface *background{nullptr};
|
||||
SDL_Color bg{0, 0, 0, 255}, fg{255, 255, 255, 255};
|
||||
int padLeft{0}, padTop{0}, padRight{0}, padBottom{0};
|
||||
int alignHoriz{ALIGN_LEFT}, alignVert{ALIGN_TOP};
|
||||
int alignContHoriz{ALIGN_LEFT}, alignContVert{ALIGN_TOP};
|
||||
TTF_Font *font{nullptr};
|
||||
};
|
||||
|
||||
class GUISAXParser {
|
||||
public:
|
||||
GUISAXParser() : state_(State::S_INITIAL), root_(nullptr) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Widget> getResult() {
|
||||
return std::move(root_);
|
||||
}
|
||||
|
||||
void startDocument() {
|
||||
printf("startDocument()\n");
|
||||
state_ = State::S_INITIAL;
|
||||
}
|
||||
|
||||
void endDocument() {
|
||||
printf("endDocument()\n");
|
||||
if (state_ != State::S_FINISHED)
|
||||
throw Exception{"Invalid state at document end"};
|
||||
}
|
||||
|
||||
void startElement(const xmlChar *name, const xmlChar **attrs) {
|
||||
printf("startElement(%s)\n", name);
|
||||
const std::string ename{reinterpret_cast<char const*>(name)};
|
||||
std::unordered_map<std::string, std::string> eattrs;
|
||||
while (*attrs != nullptr) {
|
||||
std::string k{reinterpret_cast<char const*>(*attrs)},
|
||||
v{reinterpret_cast<char const*>(*(attrs+1))};
|
||||
|
||||
eattrs[k] = v;
|
||||
attrs += 2;
|
||||
}
|
||||
|
||||
|
||||
if (state_ == State::S_STYLE)
|
||||
throw Exception{"Invalid Element inside <style>"};
|
||||
|
||||
if ((state_ == State::S_DONE) || (state_ == State::S_FINISHED))
|
||||
throw Exception{"Invalid state in startElement"};
|
||||
|
||||
if (ename == "gui") {
|
||||
if (state_ != State::S_INITIAL)
|
||||
throw Exception{"Invalid state in <gui>"};
|
||||
printf("state -> S_GUI\n");
|
||||
state_ = State::S_GUI;
|
||||
} else if (ename == "style") {
|
||||
if (state_ != State::S_GUI)
|
||||
throw Exception{"Invalid state in <style>"};
|
||||
|
||||
printf("state -> S_STYLE\n");
|
||||
state_ = State::S_STYLE;
|
||||
|
||||
auto it = eattrs.find("name");
|
||||
if (it == eattrs.end())
|
||||
throw Exception{"Style missing required attr \"name\""};
|
||||
|
||||
Style style;
|
||||
for(auto& ent : eattrs) {
|
||||
if (ent.first == "width")
|
||||
style.width = parseDimension(ent.second);
|
||||
else if (ent.first == "height")
|
||||
style.height = parseDimension(ent.second);
|
||||
else if (ent.first == "background")
|
||||
style.background = ImageProvider::getInstance().getImage(ent.second);
|
||||
else if (ent.first == "backgroundColor")
|
||||
style.bg = parseColor(ent.second);
|
||||
else if (ent.first == "color")
|
||||
style.fg = parseColor(ent.second);
|
||||
else if (ent.first == "paddingLeft")
|
||||
style.padLeft = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingTop")
|
||||
style.padTop = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingRight")
|
||||
style.padRight = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingBottom")
|
||||
style.padBottom = std::stoi(ent.second);
|
||||
else if (ent.first == "alignHorizontal")
|
||||
style.alignHoriz = parseAlign(ent.second);
|
||||
else if (ent.first == "alignVertical")
|
||||
style.alignVert = parseAlign(ent.second);
|
||||
else if (ent.first == "containerAlignHorizontal")
|
||||
style.alignContHoriz = parseAlign(ent.second);
|
||||
else if (ent.first == "containerAlignVertical")
|
||||
style.alignContVert = parseAlign(ent.second);
|
||||
else if (ent.first == "font") {
|
||||
auto fi = parseFont(ent.second);
|
||||
style.font = FontProvider::getInstance().getFont(std::get<0>(fi), std::get<1>(fi));
|
||||
} else if (ent.first == "name") {
|
||||
} else
|
||||
printf("Warning: Unknown style attribute \"%s\"\n", ent.first.c_str());
|
||||
}
|
||||
|
||||
styles_[eattrs["name"]] = style;
|
||||
} else if ((ename == "LinearLayout") ||
|
||||
(ename == "TextWidget")) {
|
||||
if ((state_ != State::S_GUI) && (state_ != State::S_WIDGETS))
|
||||
throw Exception{"Invalid state in <Widget>"};
|
||||
|
||||
std::unique_ptr<Widget> widget;
|
||||
|
||||
// Find name
|
||||
std::string name{""};
|
||||
auto it = eattrs.find("name");
|
||||
if (it != eattrs.end())
|
||||
name = it->second;
|
||||
|
||||
// Find and copy style if given
|
||||
Style style;
|
||||
it = eattrs.find("style");
|
||||
if (it != eattrs.end())
|
||||
style = styles_.at(it->second);
|
||||
|
||||
int width = style.width, height = style.height;
|
||||
|
||||
it = eattrs.find("width");
|
||||
if (it != eattrs.end())
|
||||
width = parseDimension(it->second);
|
||||
|
||||
it = eattrs.find("height");
|
||||
if (it != eattrs.end())
|
||||
height = parseDimension(it->second);
|
||||
|
||||
// Construct widget
|
||||
if (ename == "LinearLayout") {
|
||||
int direction = DIR_VERTICAL;
|
||||
it = eattrs.find("direction");
|
||||
if (it != eattrs.end()) {
|
||||
if (it->second == "horizontal")
|
||||
direction = DIR_HORIZONTAL;
|
||||
else if (it->second == "vertical")
|
||||
direction = DIR_VERTICAL;
|
||||
else
|
||||
throw Exception{"Invalid direction"};
|
||||
}
|
||||
|
||||
int childPadding = 0;
|
||||
it = eattrs.find("childPadding");
|
||||
if (it != eattrs.end())
|
||||
childPadding = std::stoi(it->second);
|
||||
|
||||
widget = make_unique<LinearLayout>(direction, width, height, name);
|
||||
static_cast<LinearLayout&>(*widget).setChildPadding(childPadding);
|
||||
} else if (ename == "TextWidget") {
|
||||
std::string text{};
|
||||
TTF_Font *font = style.font;
|
||||
|
||||
it = eattrs.find("text");
|
||||
if (it != eattrs.end())
|
||||
text = it->second;
|
||||
|
||||
it = eattrs.find("font");
|
||||
if (it != eattrs.end()) {
|
||||
auto fi = parseFont(it->second);
|
||||
font = FontProvider::getInstance().getFont(std::get<0>(fi), std::get<1>(fi));
|
||||
}
|
||||
|
||||
widget = make_unique<TextWidget>(font, text, width, height, name);
|
||||
} else
|
||||
throw Exception{"Invalid Widget"};
|
||||
|
||||
// Apply style
|
||||
for(auto& ent : eattrs) {
|
||||
if (ent.first == "background")
|
||||
style.background = ImageProvider::getInstance().getImage(ent.second);
|
||||
else if (ent.first == "backgroundColor")
|
||||
style.bg = parseColor(ent.second);
|
||||
else if (ent.first == "color")
|
||||
style.fg = parseColor(ent.second);
|
||||
else if (ent.first == "paddingLeft")
|
||||
style.padLeft = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingTop")
|
||||
style.padTop = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingRight")
|
||||
style.padRight = std::stoi(ent.second);
|
||||
else if (ent.first == "paddingBottom")
|
||||
style.padBottom = std::stoi(ent.second);
|
||||
else if (ent.first == "alignHorizontal")
|
||||
style.alignHoriz = parseAlign(ent.second);
|
||||
else if (ent.first == "alignVertical")
|
||||
style.alignVert = parseAlign(ent.second);
|
||||
else if (ent.first == "containerAlignHorizontal")
|
||||
style.alignContHoriz = parseAlign(ent.second);
|
||||
else if (ent.first == "containerAlignVertical")
|
||||
style.alignContVert = parseAlign(ent.second);
|
||||
}
|
||||
|
||||
if (style.background)
|
||||
widget->setBackground(style.background);
|
||||
|
||||
if (style.bg != SDL_Color{0, 0, 0, 255})
|
||||
widget->setBackgroundColor(style.bg);
|
||||
if (style.fg != SDL_Color{255, 255, 255, 255})
|
||||
widget->setForegroundColor(style.fg);
|
||||
|
||||
if ((style.padLeft != 0) || (style.padTop != 0) || (style.padRight != 0) ||
|
||||
(style.padBottom != 0))
|
||||
widget->setPadding(style.padLeft, style.padTop, style.padRight,
|
||||
style.padBottom);
|
||||
|
||||
if ((style.alignHoriz != ALIGN_LEFT) || (style.alignVert != ALIGN_TOP))
|
||||
widget->setAlignment(style.alignHoriz, style.alignVert);
|
||||
if ((style.alignContHoriz != ALIGN_LEFT) || (style.alignContVert != ALIGN_TOP))
|
||||
widget->setContainerAlignment(style.alignContHoriz, style.alignContVert);
|
||||
|
||||
|
||||
if (state_ == State::S_GUI) {
|
||||
printf("state -> S_WIDGETS\n");
|
||||
state_ = State::S_WIDGETS;
|
||||
|
||||
stack_.push_back(widget.get());
|
||||
root_ = std::move(widget);
|
||||
} else if (state_ == State::S_WIDGETS) {
|
||||
View* view = dynamic_cast<View*>(stack_.back());
|
||||
stack_.push_back(widget.get());
|
||||
view->addChild(std::move(widget));
|
||||
}
|
||||
} else
|
||||
throw Exception{"Invalid element"};
|
||||
}
|
||||
|
||||
void endElement(const xmlChar *name) {
|
||||
printf("endElement(%s)\n", name);
|
||||
const std::string ename{reinterpret_cast<char const*>(name)};
|
||||
|
||||
if (ename == "style") {
|
||||
if (state_ != State::S_STYLE)
|
||||
throw Exception{"Invalid state on </style>"};
|
||||
printf("state -> S_GUI\n");
|
||||
state_ = State::S_GUI;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ename == "LinearLayout") ||
|
||||
(ename == "TextWidget")) {
|
||||
if (state_ != State::S_WIDGETS)
|
||||
throw Exception{"Invalid state on </Widget>"};
|
||||
|
||||
assert(!stack_.empty());
|
||||
stack_.pop_back();
|
||||
if (stack_.empty()) {
|
||||
printf("state -> S_DONE\n");
|
||||
state_ = State::S_DONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ename == "gui") {
|
||||
if (state_ != State::S_DONE)
|
||||
throw Exception{"Invalid state on </gui>"};
|
||||
|
||||
printf("state -> S_FINISHED\n");
|
||||
state_ = State::S_FINISHED;
|
||||
return;
|
||||
}
|
||||
throw Exception{"Invalid element"};
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
S_INITIAL,
|
||||
S_GUI,
|
||||
S_STYLE,
|
||||
S_WIDGETS,
|
||||
S_DONE,
|
||||
S_FINISHED
|
||||
};
|
||||
|
||||
int parseDimension(std::string const& str) {
|
||||
if (str == "match_parent")
|
||||
return MATCH_PARENT;
|
||||
else if (str == "wrap_content")
|
||||
return WRAP_CONTENT;
|
||||
else if (str == "wrap_content_fill")
|
||||
return WRAP_CONTENT_FILL;
|
||||
|
||||
try {
|
||||
return std::stoi(str);
|
||||
} catch (std::invalid_argument &ex) {
|
||||
throw Exception{"Invalid dimension"};
|
||||
}
|
||||
}
|
||||
|
||||
int parseAlign(std::string const& str) {
|
||||
if (str == "center")
|
||||
return ALIGN_CENTER;
|
||||
else if (str == "left")
|
||||
return ALIGN_LEFT;
|
||||
else if (str == "right")
|
||||
return ALIGN_RIGHT;
|
||||
else if (str == "top")
|
||||
return ALIGN_TOP;
|
||||
else if (str == "bottom")
|
||||
return ALIGN_BOTTOM;
|
||||
|
||||
throw Exception{"Invalid alignment"};
|
||||
}
|
||||
|
||||
SDL_Color parseColor(std::string const& str) {
|
||||
SDL_Color ret;
|
||||
auto sep = str.find(',');
|
||||
if (sep == std::string::npos)
|
||||
throw Exception{"Invalid color"};
|
||||
|
||||
try {
|
||||
ret.r = std::stoi(str.substr(0, sep));
|
||||
|
||||
auto lastsep = sep;
|
||||
sep = str.find(',', sep+1);
|
||||
if (sep == std::string::npos)
|
||||
throw Exception{"Invalid color"};
|
||||
ret.g = std::stoi(str.substr(lastsep+1, sep-lastsep));
|
||||
|
||||
lastsep = sep;
|
||||
sep = str.find(',', sep+1);
|
||||
bool alpha = true;
|
||||
if (sep == std::string::npos)
|
||||
alpha = false;
|
||||
ret.b = std::stoi(str.substr(lastsep+1, sep-lastsep));
|
||||
|
||||
if (!alpha)
|
||||
ret.a = 255;
|
||||
else
|
||||
ret.a = std::stoi(str.substr(sep+1));
|
||||
} catch (std::invalid_argument &ex) {
|
||||
throw Exception{"Invalid color"};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::tuple<std::string, unsigned> parseFont(std::string const& str) {
|
||||
auto sep = str.find(',');
|
||||
if (sep == std::string::npos)
|
||||
return make_tuple(str, 12);
|
||||
else
|
||||
return make_tuple(str.substr(0, sep), std::stoi(str.substr(sep+1)));
|
||||
}
|
||||
|
||||
State state_;
|
||||
|
||||
std::vector<Widget*> stack_;
|
||||
std::unique_ptr<Widget> root_;
|
||||
std::unordered_map<std::string, Style> styles_;
|
||||
};
|
||||
|
||||
static void _startDocument(void *userdata) {
|
||||
static_cast<GUISAXParser*>(userdata)->startDocument();
|
||||
}
|
||||
|
||||
static void _endDocument(void *userdata) {
|
||||
static_cast<GUISAXParser*>(userdata)->endDocument();
|
||||
}
|
||||
|
||||
static void _startElement(void *userdata, const xmlChar *name,
|
||||
const xmlChar **attrs) {
|
||||
static_cast<GUISAXParser*>(userdata)->startElement(name, attrs);
|
||||
}
|
||||
|
||||
static void _endElement(void *userdata, const xmlChar *name) {
|
||||
static_cast<GUISAXParser*>(userdata)->endElement(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static xmlSAXHandler saxHandlers{
|
||||
nullptr, // internalSubsetSAXFunc internalSubset;
|
||||
nullptr, // isStandaloneSAXFunc isStandalone;
|
||||
nullptr, // hasInternalSubsetSAXFunc hasInternalSubset;
|
||||
nullptr, // hasExternalSubsetSAXFunc hasExternalSubset;
|
||||
nullptr, // resolveEntitySAXFunc resolveEntity;
|
||||
nullptr, // getEntitySAXFunc getEntity;
|
||||
nullptr, // entityDeclSAXFunc entityDecl;
|
||||
nullptr, // notationDeclSAXFunc notationDecl;
|
||||
nullptr, // attributeDeclSAXFunc attributeDecl;
|
||||
nullptr, // elementDeclSAXFunc elementDecl;
|
||||
nullptr, // unparsedEntityDeclSAXFunc unparsedEntityDecl;
|
||||
nullptr, // setDocumentLocatorSAXFunc setDocumentLocator;
|
||||
&_startDocument, // startDocumentSAXFunc startDocument;
|
||||
&_endDocument, // endDocumentSAXFunc endDocument;
|
||||
&_startElement, // startElementSAXFunc startElement;
|
||||
&_endElement, // endElementSAXFunc endElement;
|
||||
nullptr, // referenceSAXFunc reference;
|
||||
nullptr, // charactersSAXFunc characters;
|
||||
nullptr, // ignorableWhitespaceSAXFunc ignorableWhitespace;
|
||||
nullptr, // processingInstructionSAXFunc processingInstruction;
|
||||
nullptr, // commentSAXFunc comment;
|
||||
nullptr, // warningSAXFunc warning;
|
||||
nullptr, // errorSAXFunc error;
|
||||
nullptr, // fatalErrorSAXFunc fatalError; /* unused error() get all the errors */
|
||||
nullptr, // getParameterEntitySAXFunc getParameterEntity;
|
||||
nullptr, // cdataBlockSAXFunc cdataBlock;
|
||||
nullptr, // externalSubsetSAXFunc externalSubset;
|
||||
false, // unsigned int initialized;
|
||||
// /* The following fields are extensions available only on version 2 */
|
||||
nullptr, // void *_private;
|
||||
nullptr, // startElementNsSAX2Func startElementNs;
|
||||
nullptr, // endElementNsSAX2Func endElementNs;
|
||||
nullptr // xmlStructuredErrorFunc serror;
|
||||
};
|
||||
|
||||
std::unique_ptr<Widget> loadGUIFromFile(std::string const& filename)
|
||||
{
|
||||
GUISAXParser parser{};
|
||||
xmlSAXUserParseFile(&saxHandlers, &parser, filename.c_str());
|
||||
|
||||
return parser.getResult();
|
||||
}
|
||||
Reference in New Issue
Block a user