From 884fd8bb5250ff5336d27b1ce6de8e081617ff61 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Tue, 10 Mar 2015 00:48:25 +0100 Subject: [PATCH] Load GUI from XML --- FontProvider.cc | 32 ++++ FontProvider.hh | 42 +++++ GUILoader.cc | 425 +++++++++++++++++++++++++++++++++++++++++++++++ GUILoader.hh | 11 ++ ImageProvider.cc | 33 ++++ ImageProvider.hh | 28 ++++ LinearLayout.cc | 134 ++++++++++++--- LinearLayout.hh | 4 +- Makefile | 6 +- Singleton.hh | 24 +++ TextWidget.cc | 96 +++++++---- TextWidget.hh | 9 +- VBOManager.cc | 17 +- VBOManager.hh | 10 +- View.cc | 44 ++++- View.hh | 9 +- Widget.cc | 107 ++++++++++-- Widget.hh | 28 +++- common.hh | 11 +- layouts/test.xml | 19 +++ main.cc | 49 +++++- 21 files changed, 1019 insertions(+), 119 deletions(-) create mode 100644 FontProvider.cc create mode 100644 FontProvider.hh create mode 100644 GUILoader.cc create mode 100644 GUILoader.hh create mode 100644 ImageProvider.cc create mode 100644 ImageProvider.hh create mode 100644 Singleton.hh create mode 100644 layouts/test.xml diff --git a/FontProvider.cc b/FontProvider.cc new file mode 100644 index 0000000..1966005 --- /dev/null +++ b/FontProvider.cc @@ -0,0 +1,32 @@ +#include + +#include "FontProvider.hh" + +template<> +std::unique_ptr Singleton::instance{nullptr}; +template<> +std::once_flag Singleton::instance_flag{}; + +FontProvider::FontProvider() +{ +} + +TTF_Font *FontProvider::getFont(std::string const& name, unsigned ptsize) +{ + auto key = make_tuple(name, ptsize); + auto it = fontCache_.find(key); + + if (it == fontCache_.end()) { + TTFFontUPtr font{TTF_OpenFont(name.c_str(), ptsize)}; + if (!font) + throw TTFException{}; + std::tie(it, std::ignore) = fontCache_.insert(make_pair(key, std::move(font))); + } + + return it->second.get(); +} + +void FontProvider::cleanup() +{ + fontCache_.clear(); +} diff --git a/FontProvider.hh b/FontProvider.hh new file mode 100644 index 0000000..873e9b4 --- /dev/null +++ b/FontProvider.hh @@ -0,0 +1,42 @@ +#ifndef __OPENGLPLAYGROUND_FONTPROVIDER_HH__ +#define __OPENGLPLAYGROUND_FONTPROVIDER_HH__ + +#include +#include +#include +#include + +#include "common.hh" +#include "Singleton.hh" + +using FontProviderKeyType = std::tuple; + +namespace std +{ + template<> + struct hash { + typedef FontProviderKeyType argument_type; + typedef std::size_t result_type; + + result_type operator()(argument_type const& s) const { + const result_type h1{std::hash()(std::get<0>(s))}; + const result_type h2{std::hash()(std::get<1>(s))}; + return h1 ^ h2; + } + }; +} + +class FontProvider : public Singleton { +private: + FontProvider(); + friend class Singleton; + +public: + TTF_Font *getFont(std::string const& name, unsigned ptsize); + + void cleanup(); + +private: + std::unordered_map, TTFFontUPtr> fontCache_; +}; +#endif diff --git a/GUILoader.cc b/GUILoader.cc new file mode 100644 index 0000000..48beb4b --- /dev/null +++ b/GUILoader.cc @@ -0,0 +1,425 @@ +#include + +#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 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(name)}; + std::unordered_map eattrs; + while (*attrs != nullptr) { + std::string k{reinterpret_cast(*attrs)}, + v{reinterpret_cast(*(attrs+1))}; + + eattrs[k] = v; + attrs += 2; + } + + + if (state_ == State::S_STYLE) + throw Exception{"Invalid Element inside "}; + 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 "}; + + 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 "}; + + 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 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 stack_; + std::unique_ptr root_; + std::unordered_map styles_; +}; + +static void _startDocument(void *userdata) { + static_cast(userdata)->startDocument(); +} + +static void _endDocument(void *userdata) { + static_cast(userdata)->endDocument(); +} + +static void _startElement(void *userdata, const xmlChar *name, + const xmlChar **attrs) { + static_cast(userdata)->startElement(name, attrs); +} + +static void _endElement(void *userdata, const xmlChar *name) { + static_cast(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 loadGUIFromFile(std::string const& filename) +{ + GUISAXParser parser{}; + xmlSAXUserParseFile(&saxHandlers, &parser, filename.c_str()); + + return parser.getResult(); +} diff --git a/GUILoader.hh b/GUILoader.hh new file mode 100644 index 0000000..6fff6f9 --- /dev/null +++ b/GUILoader.hh @@ -0,0 +1,11 @@ +#ifndef __OPENGLPLAYGROUND_GUILOADER_HH__ +#define __OPENGLPLAYGROUND_GUILOADER_HH__ + +#include +#include + +#include "Widget.hh" + +std::unique_ptr loadGUIFromFile(std::string const& filename); + +#endif diff --git a/ImageProvider.cc b/ImageProvider.cc new file mode 100644 index 0000000..9faa7d5 --- /dev/null +++ b/ImageProvider.cc @@ -0,0 +1,33 @@ +#include + +#include "ImageProvider.hh" + +template<> +std::unique_ptr Singleton::instance{nullptr}; + +template<> +std::once_flag Singleton::instance_flag{}; + +ImageProvider::ImageProvider() +{ +} + +SDL_Surface *ImageProvider::getImage(std::string const& name) +{ + auto it = imageCache_.find(name); + + if(it == imageCache_.end()) { + SDLSurfaceUPtr image{IMG_Load(name.c_str())}; + if (!image) + throw SDLException{}; + tie(it, std::ignore) = imageCache_.insert(make_pair(name, std::move(image))); + } + + return it->second.get(); +} + + +void ImageProvider::cleanup() +{ + imageCache_.clear(); +} diff --git a/ImageProvider.hh b/ImageProvider.hh new file mode 100644 index 0000000..1d6d1b7 --- /dev/null +++ b/ImageProvider.hh @@ -0,0 +1,28 @@ +#ifndef __OPENGLPLAYGROUND_IMAGEPROVIDER_HH__ +#define __OPENGLPLAYGROUND_IMAGEPROVIDER_HH__ + +#include +#include +#include +#include + +#include + +#include "common.hh" +#include "Singleton.hh" + +class ImageProvider : public Singleton { +private: + ImageProvider(); + friend class Singleton; + +public: + SDL_Surface* getImage(std::string const& name); + + void cleanup(); + +private: + std::unordered_map imageCache_; +}; + +#endif diff --git a/LinearLayout.cc b/LinearLayout.cc index fb2eb6d..bf1d806 100644 --- a/LinearLayout.cc +++ b/LinearLayout.cc @@ -2,7 +2,7 @@ #include "LinearLayout.hh" LinearLayout::LinearLayout(int direction, int width, int height, std::string name) - : View(width, height, std::move(name)), direction_(direction) + : View(width, height, std::move(name)), direction_(direction), padChildren_(0) { } @@ -19,59 +19,149 @@ void LinearLayout::setChildPadding(int pad) } } -void LinearLayout::layout() +void LinearLayout::layout(Widget *caller) { - // TODO: Respect parent padding - if ((width_ == MATCH_PARENT) && parent_) - realWidth_ = parent_->getRealWidth(); - - if ((height_ == MATCH_PARENT) && parent_) - realHeight_ = parent_->getRealHeight(); + int width, height; + std::tie(width, height) = Widget::baseLayout(caller); + bool widthChanged = false, heightChanged = false; + if (direction_ == DIR_VERTICAL) { int contentWidth = 0; - if (width_ == WRAP_CONTENT) { + if (width == 0) { + // Clear realWidth_ to signal WRAP_CONTENT_FILL children to use WRAP_CONTENT + realWidth_ = 0; // Determine content width + if (children_.size() == 0) + return; for(auto& ent : children_) { auto& child = *(std::get<1>(ent)); assert(child.getHeight() != MATCH_PARENT); if (child.getWidth() == MATCH_PARENT) continue; - if (child.getWidth() == WRAP_CONTENT) + if ((child.getWidth() == WRAP_CONTENT) || (child.getWidth() == WRAP_CONTENT_FILL)) _layout(child); contentWidth = std::max(contentWidth, child.getRealWidth()); } assert(contentWidth > 0); - realWidth_ = contentWidth + padLeft_ + padRight_; + if (realWidth_ != contentWidth + padLeft_ + padRight_) { + realWidth_ = contentWidth + padLeft_ + padRight_; + widthChanged = true; + } printf("Inner width: %d, outer width: %d\n", contentWidth, realWidth_); - } - - int currentY = padTop_; + } else + contentWidth = realWidth_ - padLeft_ - padRight_; // Layout children + int currentY = padTop_; + for(auto& ent : children_) { auto& child = *(std::get<1>(ent)); if (child.getWidth() == MATCH_PARENT) _layout(child); - + // Second layout pass for WRAP_CONTENT_FILL children + if ((child.getWidth() == WRAP_CONTENT_FILL) && + (child.getRealWidth() != contentWidth)) + _layout(child); + SDL_Rect cr; cr.x = padLeft_; cr.y = currentY; - if ((realWidth_ - padLeft_ - padRight_) > child.getRealWidth()) + if (contentWidth > child.getRealWidth()) { cr.w = child.getRealWidth(); - else - cr.w = realWidth_ - padLeft_ - padRight_; + auto space = contentWidth - child.getRealWidth(); + if (child.getContainerAlignmentHoriz() == ALIGN_CENTER) + cr.x += space/2; + else if (child.getContainerAlignmentHoriz() == ALIGN_RIGHT) + cr.x += space; + } else + cr.w = contentWidth; cr.h = child.getRealHeight(); std::get<2>(ent) = cr; - + currentY += child.getRealHeight() + padChildren_; } - if (height_ == WRAP_CONTENT) - realHeight_ = currentY - padChildren_ + padBottom_; - } + if (height == 0) + if (realHeight_ != currentY - padChildren_ + padBottom_) { + realHeight_ = currentY - padChildren_ + padBottom_; + heightChanged = true; + } + } else { // direction == DIR_HORIZONTAL + int contentHeight = 0; + + if (height == 0) { + // Clear realHeight_ to signal WRAP_CONTENT_FILL children to use WRAP_CONTENT + realHeight_ = 0; + // Determine content height + if (children_.size() == 0) + return; + for(auto& ent : children_) { + auto& child = *(std::get<1>(ent)); + assert(child.getWidth() != MATCH_PARENT); + if (child.getHeight() == MATCH_PARENT) + continue; + if ((child.getHeight() == WRAP_CONTENT) || (child.getHeight() == WRAP_CONTENT_FILL)) + _layout(child); + contentHeight = std::max(contentHeight, child.getRealHeight()); + } + assert(contentHeight > 0); + if (realHeight_ != contentHeight + padTop_ + padBottom_) { + realHeight_ = contentHeight + padTop_ + padBottom_; + heightChanged = true; + } + printf("Inner height: %d, outer height: %d\n", contentHeight, realHeight_); + } else + contentHeight = realHeight_ - padTop_ - padBottom_; + + // Layout children + int currentX = padLeft_; + + for(auto& ent : children_) { + auto& child = *(std::get<1>(ent)); + if (child.getHeight() == MATCH_PARENT) + _layout(child); + // Second layout pass for WRAP_CONTENT_FILL children + if ((child.getHeight() == WRAP_CONTENT_FILL) && + (child.getRealHeight() != contentHeight)) + _layout(child); + + SDL_Rect cr; + cr.x = currentX; + cr.y = padTop_; + cr.w = child.getRealWidth(); + if (contentHeight > child.getRealHeight()) { + cr.h = child.getRealHeight(); + auto space = contentHeight - child.getRealHeight(); + if (child.getContainerAlignmentVert() == ALIGN_CENTER) + cr.y += space/2; + else if (child.getContainerAlignmentVert() == ALIGN_BOTTOM) + cr.y += space; + } else + cr.h = contentHeight; + + std::get<2>(ent) = cr; + + currentX += child.getRealWidth() + padChildren_; + } + + if (width == 0) + if (realWidth_ != currentX - padChildren_ + padRight_) { + realWidth_ = currentX - padChildren_ + padRight_; + widthChanged = true; + } + } + + if (parent_ && (parent_ != caller) && + ((widthChanged && ((parent_->getWidth() == WRAP_CONTENT) || + (parent_->getWidth() == WRAP_CONTENT_FILL))) || + (heightChanged && ((parent_->getHeight() == WRAP_CONTENT) || + (parent_->getHeight() == WRAP_CONTENT_FILL))))) + _layout(*parent_); + + printf("Layout %s now %d, %d\n", name_.c_str(), realWidth_, realHeight_); invalidateGL(); } diff --git a/LinearLayout.hh b/LinearLayout.hh index 3a3acfd..33d315a 100644 --- a/LinearLayout.hh +++ b/LinearLayout.hh @@ -4,7 +4,7 @@ #include "View.hh" static const int DIR_VERTICAL = 0; -static const int DIR_HORIZONTAL = 0; +static const int DIR_HORIZONTAL = 1; class LinearLayout : public View { public: @@ -18,7 +18,7 @@ public: void setChildPadding(int pad); protected: - void layout() override; + void layout(Widget* caller = nullptr) override; int direction_; int padChildren_; diff --git a/Makefile b/Makefile index af70b99..99c1c0e 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CXX=g++ -CXXOPTS=-Og -ggdb -Wall -Wextra -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-sign-compare -std=c++14 -flto +CXXOPTS=-Og -ggdb -Wall -Wextra -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-sign-compare -std=c++14 -flto -I/usr/include/libxml2 LDOPTS= -LIBS=-lglbinding -lSDL2 -lSDL2_image -lobj -lSDL2_ttf -lprotobuf -CXXSRCS=main.cc objectParser.cc shaders.cc Object.cc VBOManager.cc texture.cc font.cc Overlay.cc TextWidget.cc Widget.cc View.cc LinearLayout.cc +LIBS=-lglbinding -lSDL2 -lSDL2_image -lobj -lSDL2_ttf -lprotobuf -lxml2 +CXXSRCS=main.cc objectParser.cc shaders.cc Object.cc VBOManager.cc texture.cc font.cc Overlay.cc TextWidget.cc Widget.cc View.cc LinearLayout.cc FontProvider.cc ImageProvider.cc GUILoader.cc BINIFY_SRCS=binifyObj.cc objectParser.cc object.pb.cc OBJS=$(addprefix objs/,$(CXXSRCS:.cc=.o)) BINIFY_OBJS=$(addprefix objs/,$(BINIFY_SRCS:.cc=.o)) diff --git a/Singleton.hh b/Singleton.hh new file mode 100644 index 0000000..d88542c --- /dev/null +++ b/Singleton.hh @@ -0,0 +1,24 @@ +#ifndef __OPENGLPLAYGROUND_SINGLETON_HH__ +#define __OPENGLPLAYGROUND_SINGLETON_HH__ + +#include +#include + +template +class Singleton { +public: + static T& getInstance() { + call_once(instance_flag, init); + return *instance; + } + +private: + static std::unique_ptr instance; + static std::once_flag instance_flag; + + static void init() { + instance.reset(new T{}); + } +}; + +#endif diff --git a/TextWidget.cc b/TextWidget.cc index f102a08..69a2093 100644 --- a/TextWidget.cc +++ b/TextWidget.cc @@ -3,8 +3,8 @@ #include "font.hh" #include "TextWidget.hh" -TextWidget::TextWidget(Font& font, std::string text, int width, int height) - : Widget(width, height), text_(std::move(text)), font_(font), +TextWidget::TextWidget(TTF_Font *font, std::string text, int width, int height, std::string name) + : Widget(width, height, std::move(name)), text_(std::move(text)), font_(font), textSurf_(nullptr) { layout(); @@ -19,6 +19,7 @@ void TextWidget::setText(std::string text) if (text != text_) { text_ = std::move(text); invalidateGL(); + textSurf_.release(); layout(); } } @@ -28,49 +29,72 @@ void TextWidget::setForegroundColor(SDL_Color fg) Widget::setForegroundColor(fg); // Trigger text rerender + textSurf_.release(); layout(); } -void TextWidget::layout() +void TextWidget::layout(Widget *caller) { - // Determine text size int width, height; - if(TTF_SizeUTF8(font_.getFont(), text_.c_str(), &width, &height) != 0) - throw TTFException{}; + std::tie(width, height) = Widget::baseLayout(caller); - if ((width > (width_ - padLeft_ - padRight_)) && (width_ != WRAP_CONTENT)) { - textSurf_.reset(TTF_RenderUTF8_Blended_Wrapped(font_.getFont(), - text_.c_str(), - fg_, width_- padLeft_ - padRight_)); - if (!textSurf_) + // Determine text size + int textWidth, textHeight; + if ((height == 0) || (width == 0)) { + if(TTF_SizeUTF8(font_, text_.c_str(), &textWidth, &textHeight) != 0) throw TTFException{}; - - if (height_ == WRAP_CONTENT) - // TODO: Get proper tight bounding box for multiline text - if (realHeight_ != (textSurf_->h - // TTF_FontLineSkip(font_.getFont()) - + padTop_ + padBottom_)) { - realHeight_ = textSurf_->h - // TTF_FontLineSkip(font_.getFont()) - + padTop_ + padBottom_; - invalidateGL(); - } - } else { - textSurf_.reset(TTF_RenderUTF8_Blended(font_.getFont(), text_.c_str(), fg_)); - - if (!textSurf_) - throw TTFException{}; - - if (width_ == WRAP_CONTENT) - if (realWidth_ != (textSurf_->w + padLeft_ + padRight_)) { - realWidth_ = textSurf_->w + padLeft_ + padRight_; - invalidateGL(); - } - if (height_ == WRAP_CONTENT) - if (realHeight_ != (textSurf_->h + padTop_ + padBottom_)) { - realHeight_ = textSurf_->h + padTop_ + padBottom_; - invalidateGL(); - } } + bool multiline = false; + if (height == 0) { + if (width != 0) { + textSurf_.reset(TTF_RenderUTF8_Blended_Wrapped(font_, + text_.c_str(), + fg_, width - padLeft_ - padRight_)); + if (!textSurf_) + throw TTFException{}; + + multiline = true; + // TODO: Get proper tight bounding box for multiline text + height = textSurf_->h - // TTF_FontLineSkip(font_.getFont()) + + padTop_ + padBottom_; + } else { + width = textWidth + padLeft_ + padRight_; + height = textHeight + padTop_ + padBottom_; + } + } + + if (width == 0) { + width = textWidth + padLeft_ + padRight_; + } + + if ((width != realWidth_) || (height != realHeight_) || !textSurf_) { + if (!multiline) { + textSurf_.reset(TTF_RenderUTF8_Blended(font_, text_.c_str(), fg_)); + + if (!textSurf_) + throw TTFException{}; + } + + if ((width != realWidth_) || (height != realHeight_)) { + bool parentLayout = false; + if (parent_ && (caller != parent_) && + (((width != realWidth_) && ((parent_->getWidth() == WRAP_CONTENT) || + (parent_->getWidth() == WRAP_CONTENT_FILL))) || + ((height != realHeight_) && ((parent_->getHeight() == WRAP_CONTENT) || + (parent_->getHeight() == WRAP_CONTENT_FILL))))) + parentLayout = true; + + realWidth_ = width; + realHeight_ = height; + if (parentLayout) + _layout(*parent_); + + printf("TextWidget %s now %d, %d\n", name_.c_str(), realWidth_, realHeight_); + } + + invalidateGL(); + } } void TextWidget::render(SDL_Surface *dst, SDL_Rect *dstRect) const diff --git a/TextWidget.hh b/TextWidget.hh index 26306de..c11424a 100644 --- a/TextWidget.hh +++ b/TextWidget.hh @@ -10,8 +10,9 @@ class Font; class TextWidget final : public Widget { public: - TextWidget(Font& font, std::string text = "", - int width = WRAP_CONTENT, int height = WRAP_CONTENT); + TextWidget(TTF_Font *font, std::string text = "", + int width = WRAP_CONTENT, int height = WRAP_CONTENT, + std::string name = ""); TextWidget(TextWidget const& copy) = delete; TextWidget& operator=(TextWidget const& copy) = delete; @@ -26,11 +27,11 @@ public: void render(SDL_Surface *dst, SDL_Rect *dstRect) const override; protected: - void layout() override; + void layout(Widget* caller = nullptr) override; private: std::string text_; - Font& font_; + TTF_Font *font_; SDLSurfaceUPtr textSurf_; }; diff --git a/VBOManager.cc b/VBOManager.cc index b0e31f8..a36756e 100644 --- a/VBOManager.cc +++ b/VBOManager.cc @@ -7,19 +7,10 @@ using namespace gl; -std::unique_ptr VBOManager::instance{nullptr}; -static std::once_flag instance_flag; - -VBOManager& VBOManager::getInstance() -{ - std::call_once(instance_flag, init); - return *instance; -} - -void VBOManager::init() -{ - instance.reset(new VBOManager{}); -} +template<> +std::unique_ptr Singleton::instance{nullptr}; +template<> +std::once_flag Singleton::instance_flag{}; VBOManager::VBOManager() { diff --git a/VBOManager.hh b/VBOManager.hh index 6d35bab..fc72852 100644 --- a/VBOManager.hh +++ b/VBOManager.hh @@ -8,10 +8,10 @@ #include -class VBOManager { +#include "Singleton.hh" + +class VBOManager : public Singleton { public: - static VBOManager& getInstance(); - ~VBOManager(); VBOManager(VBOManager const& copy) = delete; @@ -19,9 +19,7 @@ public: private: VBOManager(); - - static void init(); - static std::unique_ptr instance; + friend class Singleton; class VBO; diff --git a/View.cc b/View.cc index be6c0f4..f602dfd 100644 --- a/View.cc +++ b/View.cc @@ -17,7 +17,7 @@ void View::addChild(std::unique_ptr child) layout(); } -Widget& View::getChildByName(std::string name) +Widget& View::getChildByName(std::string const& name) { if (name == "") throw ChildNotFoundException{}; @@ -30,7 +30,7 @@ Widget& View::getChildByName(std::string name) throw ChildNotFoundException{}; } -Widget const& View::getChildByName(std::string name) const +Widget const& View::getChildByName(std::string const& name) const { if (name == "") throw ChildNotFoundException{}; @@ -43,7 +43,45 @@ Widget const& View::getChildByName(std::string name) const throw ChildNotFoundException{}; } -std::unique_ptr View::removeChild(std::string name) +Widget& View::getPath(std::string const& path) +{ + auto slash = path.find('/'); + + // Last path element + if (slash == std::string::npos) + return getChildByName(path); + + auto first = path.substr(0, slash); + auto& child = getChildByName(first); + try { + View& next = dynamic_cast(child); + + return next.getPath(path.substr(slash+1)); + } catch(std::bad_cast &ex) { + throw ChildNotFoundException{}; + } +} + +Widget const& View::getPath(std::string const& path) const +{ + auto slash = path.find('/'); + + // Last path element + if (slash == std::string::npos) + return getChildByName(path); + + auto first = path.substr(0, slash); + auto& child = getChildByName(first); + try { + View const& next = dynamic_cast(child); + + return next.getPath(path.substr(slash+1)); + } catch(std::bad_cast &ex) { + throw ChildNotFoundException{}; + } +} + +std::unique_ptr View::removeChild(std::string const& name) { if (name == "") throw ChildNotFoundException{}; diff --git a/View.hh b/View.hh index a34bff6..60b14b8 100644 --- a/View.hh +++ b/View.hh @@ -33,11 +33,14 @@ public: void addChild(std::unique_ptr child); - Widget& getChildByName(std::string name); + Widget& getChildByName(std::string const& name); - Widget const& getChildByName(std::string name) const; + Widget const& getChildByName(std::string const& name) const; - std::unique_ptr removeChild(std::string name); + Widget& getPath(std::string const& path); + Widget const& getPath(std::string const& path) const; + + std::unique_ptr removeChild(std::string const& name); std::unique_ptr removeChild(Widget& child); void render(SDL_Surface *dst, SDL_Rect *dstRect) const override; diff --git a/Widget.cc b/Widget.cc index 49a9ba7..11833dd 100644 --- a/Widget.cc +++ b/Widget.cc @@ -13,16 +13,22 @@ Widget::Widget(int width, int height, std::string name) bg_(SDL_Color{0, 0, 0, 0}), fg_(SDL_Color{255, 255, 255, 255}), padLeft_(0), padTop_(0), padRight_(0), padBottom_(0), alignHoriz_(ALIGN_LEFT), alignVert_(ALIGN_TOP), + alignContHoriz_(ALIGN_LEFT), alignContVert_(ALIGN_TOP), + parent_(nullptr), renderTop_(0), renderLeft_(0), renderTexValid_(false), renderAttribsValid_(false), renderTex_(nullptr), vaID_(0) { - if (width_ == WRAP_CONTENT) + if ((width_ == WRAP_CONTENT) || + (width_ == MATCH_PARENT) || + (width_ == WRAP_CONTENT_FILL)) realWidth_ = 0; else realWidth_ = width_; - if (height_ == WRAP_CONTENT) + if ((height_ == WRAP_CONTENT) || + (height_ == MATCH_PARENT) || + (height_ == WRAP_CONTENT_FILL)) realHeight_ = 0; else realHeight_ = height_; @@ -38,18 +44,33 @@ void Widget::setSize(int width, int height) if ((width_ != width) || (height_ != height)) { width_ = width; height_ = height; - - if (width_ != WRAP_CONTENT) - if (realWidth_ != width_) { - realWidth_ = width_; - } - if (height_ != WRAP_CONTENT) - if (realHeight_ != height_) { - realHeight_ = height_; - } + if (width_ == MATCH_PARENT) { + if (parent_) + realWidth_ = parent_->realWidth_ - parent_->padLeft_ - parent_->padRight_; + else + realWidth_ = 0; + } else if ((width_ == WRAP_CONTENT) || + (width_ == WRAP_CONTENT_FILL)) + realWidth_ = 0; + else + realWidth_ = width_; - layout(); + if (height_ == MATCH_PARENT) { + if (parent_) + realHeight_ = parent_->realHeight_ - parent_->padTop_ - parent_->padBottom_; + else + realHeight_ = 0; + } else if ((height_ == WRAP_CONTENT) || + (height_ == WRAP_CONTENT_FILL)) + realHeight_ = 0; + else + realHeight_ = height_; + + if (parent_) + parent_->layout(); + else + layout(); } } @@ -102,9 +123,32 @@ void Widget::setAlignment(int horiz, int vert) { } } +void Widget::setContainerAlignment(int horiz, int vert) { + if ((alignContHoriz_ != horiz) || + (alignContVert_ != vert)) { + alignContHoriz_ = horiz; + alignContVert_ = vert; + + if (parent_) + parent_->layout(); + } +} + void Widget::setParent(Widget *parent) { parent_ = parent; + if (width_ == MATCH_PARENT) { + if (parent_ && (parent_->realWidth_ > 0)) + realWidth_ = parent_->realWidth_ - parent_->padLeft_ - parent_->padRight_; + else + realWidth_ = 0; + } + if (height_ == MATCH_PARENT) { + if (parent_ && (parent_->realHeight_ > 0)) + realHeight_ = parent_->realHeight_ - parent_->padTop_ - parent_->padBottom_; + else + realHeight_ = 0; + } } void Widget::setGLRenderPos(int left, int top) @@ -205,11 +249,47 @@ SDL_Rect Widget::calcContentRect(SDL_Surface *dst, SDL_Rect *dstRect, SDL_Rect c return rect; } +std::tuple Widget::baseLayout(Widget *caller) +{ + int width = realWidth_, height = realHeight_; + if ((width_ == MATCH_PARENT) && parent_) { + if ((parent_->getRealWidth() <= 0) && (parent_ != caller)) + parent_->layout(); + width = parent_->getRealWidth() - parent_->getLeftPadding() - parent_->getRightPadding(); + } else if ((width_ == WRAP_CONTENT_FILL) && parent_ && (parent_->getRealWidth() > 0)) { + width = parent_->getRealWidth() - parent_->getLeftPadding() - parent_->getRightPadding(); + } else if ((width_ == WRAP_CONTENT) || (width_ == WRAP_CONTENT_FILL)) { + width = 0; + } + + if ((height_ == MATCH_PARENT) && parent_) { + if ((parent_->getRealHeight() <= 0) && (parent_ != caller)) + parent_->layout(); + height = parent_->getRealHeight() - parent_->getTopPadding() - parent_->getBottomPadding(); + } else if ((height_ == WRAP_CONTENT_FILL) && parent_ && (parent_->getRealHeight() > 0)) { + height = parent_->getRealHeight() - parent_->getTopPadding() - parent_->getBottomPadding(); + } else if ((height_ == WRAP_CONTENT) || (height_ == WRAP_CONTENT_FILL)) { + height = 0; + } + + printf("baseLayout: %s: %d, %d\n", name_.c_str(), width, height); + + return std::make_tuple(width, height); +} + +void Widget::invalidateGL() +{ + renderTexValid_ = false; + if (parent_) + parent_->invalidateGL(); +} + void Widget::renderToGL() const { const size_t NUM_TRIANGLES = 6; if (!renderTexValid_) { + printf("Widget::renderToGL: redraw texture for %s\n", name_.c_str()); uint32_t rmask, gmask, bmask, amask; int bpp; if (!SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888, @@ -235,7 +315,8 @@ void Widget::renderToGL() const renderTex_->bind(); if (!renderAttribsValid_) { - int t = renderTop_, l = renderLeft_, b = renderTop_+realHeight_, + printf("Widget::renderToGL: regen attribs for %s\n", name_.c_str()); + short int t = renderTop_, l = renderLeft_, b = renderTop_+realHeight_, r = renderLeft_+realWidth_; std::vector vertexAttribs{ {{l, t}, {0, 0}}, diff --git a/Widget.hh b/Widget.hh index 4f03bee..9f5a3c6 100644 --- a/Widget.hh +++ b/Widget.hh @@ -15,6 +15,9 @@ static const int WRAP_CONTENT = -1; WRAP_CONTENT */ static const int MATCH_PARENT = -2; +/* Size the widget to wrap the content size, expanding to fill parent when + parent is larger */ +static const int WRAP_CONTENT_FILL = -3; static const int ALIGN_TOP = 0; static const int ALIGN_BOTTOM = 1; @@ -56,8 +59,16 @@ public: the background is applied to the entire dstRect, and the content is rendered with padding*/ void setPadding(int left, int top, int right, int bottom); + int getLeftPadding() const { return padLeft_; } + int getTopPadding() const { return padTop_; } + int getRightPadding() const { return padRight_; } + int getBottomPadding() const { return padBottom_; } virtual void setAlignment(int horiz, int vert); + + void setContainerAlignment(int horiz, int vert); + int getContainerAlignmentHoriz() const { return alignContHoriz_; } + int getContainerAlignmentVert() const { return alignContVert_; } void setGLRenderPos(int left, int top); @@ -66,23 +77,29 @@ public: protected: /* Update realWidth_ and realHeight_ where necessary - (width_ or height_ == WRAP_CONTENT) */ - virtual void layout() = 0; + (width_ or height_ == WRAP_CONTENT). Check caller to prevent recursion */ + virtual void layout(Widget *caller = nullptr) = 0; /* Inform the renderToGL() code that any cached presentations are now invalid */ - void invalidateGL() { renderTexValid_ = false; } + void invalidateGL(); /* Helper to calculate the SDL_Rect with which to render the content, respecting alignment and padding */ SDL_Rect calcContentRect(SDL_Surface *dst, SDL_Rect *dstRect, SDL_Rect contentRect) const; + /* Helper to calculate layout height, width constraints + Returns width, height tuple with with width/height > 0 if fixed or + determined by parent layout, 0 if it should be based on widget contents. + Checks caller to prevent recursion */ + std::tuple baseLayout(Widget *caller); + void setParent(Widget *parent); /* Helpers to allow Views to access protected methods of other Widgets */ void _setParent(Widget& child) { child.setParent(this); } void _clearParent(Widget& child) { child.setParent(nullptr); } - void _layout(Widget& child) { child.layout(); } - + void _layout(Widget& child) { child.layout(this); } + std::string name_; int width_, height_; @@ -94,6 +111,7 @@ protected: int padLeft_, padTop_, padRight_, padBottom_; int alignHoriz_, alignVert_; + int alignContHoriz_, alignContVert_; Widget *parent_; diff --git a/common.hh b/common.hh index d3f0141..a0510d6 100644 --- a/common.hh +++ b/common.hh @@ -141,14 +141,21 @@ enum class VAFormats { // Some helpers to C++11-ify SDL struct SDLSurfaceDeleter { - void operator()(SDL_Surface* ptr) const - { + void operator()(SDL_Surface* ptr) const { SDL_FreeSurface(ptr); } }; using SDLSurfaceUPtr = std::unique_ptr; +struct TTFFontDeleter { + void operator()(TTF_Font* font) const { + TTF_CloseFont(font); + } +}; + +using TTFFontUPtr = std::unique_ptr; + class SDLSurfaceScopedLock { public: SDLSurfaceScopedLock(SDL_Surface *surf) : surf_(surf) { diff --git a/layouts/test.xml b/layouts/test.xml new file mode 100644 index 0000000..78bde58 --- /dev/null +++ b/layouts/test.xml @@ -0,0 +1,19 @@ + +