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 @@ + +