widget.h and widget.cpp
#ifndef WIDGET_HPP#define WIDGET_HPP#include #include using std::vector;class Widget{ public: enum Visibility { VISIBLE, HIDDEN, COLLAPSED }; bool isModal; sf::Vector2i pos; Widget() : parentPtr(NULL), isModal(false), pos(0, 0), visibility(VISIBLE) {} virtual ~Widget() {} bool addChild(Widget &w); bool removeChild(const Widget &w); int childrenCount() const; Widget * const getChildPtr(const int index); Widget * const getParentPtr(); // Is w an ancestor of this widget? bool hasAncestor(const Widget &ancestor) const; // Is w a descendant of this widget? bool hasDescendant(const Widget &descendant) const; virtual int getWidth() const = 0; virtual int getHeight() const = 0; virtual bool setWidth(const int newWidth) = 0; virtual bool setHeight(const int newHeight) = 0; Visibility getVisibility() const; void setVisibility(const Visibility newVisibility); sf::Vector2i getScreenPos() const; bool containsLocalPos(const sf::Vector2i &local) const; bool containsScreenPos(const sf::Vector2i &screen) const; Widget *mouseDown(const sf::Vector2i &mLocal); virtual void mouseMove(const sf::Vector2i &mLocal, const sf::Vector2i δ) = 0; virtual void mouseUp(const sf::Vector2i &mLocal) = 0; virtual void keyDown(const sf::Event::KeyEvent keyEvent) = 0; virtual void keyUp(const sf::Event::KeyEvent keyEvent) = 0; virtual void textInput(const sf::Event::TextEvent textEvent) = 0; virtual void gainFocus() = 0; virtual void loseFocus() = 0; void update(); void draw() const; private: vector children; Widget *parentPtr; Visibility visibility; virtual void mouseDownEvent(const sf::Vector2i &mLocal) = 0; virtual void doUpdate() = 0; virtual void doDraw() const = 0;};typedef vector::iterator WidgetIt;typedef vector::const_iterator ConstWidgetIt;typedef vector::reverse_iterator WidgetRIt;#endif
#include "widget.h"#include using std::find;bool Widget::addChild(Widget &w){ bool result = false; // A widget is not allowed to become its own child or grandchild if ((&w != this) && !hasAncestor(w)) { if ((w.parentPtr != NULL) && (w.parentPtr == this)) { w.parentPtr->removeChild(w); } children.push_back(&w); w.parentPtr = this; result = true; } return result;}bool Widget::removeChild(const Widget &w){ bool result = false; if (w.parentPtr == this) { children.erase(find(children.begin(), children.end(), &w)); result = true; } return result;}int Widget::childrenCount() const{ return children.size();}Widget * const Widget::getChildPtr(const int index){ return children.at(index);}Widget * const Widget::getParentPtr(){ return parentPtr;}bool Widget::hasAncestor(const Widget &ancestor) const{ return (parentPtr != NULL) && ((parentPtr == &ancestor) || (parentPtr->hasAncestor(ancestor)));}bool Widget::hasDescendant(const Widget &descendant) const{ bool result = false; for (ConstWidgetIt it = children.begin(); it != children.end(); ++it) { if ((*it == &descendant) || ((*it)->hasDescendant(descendant))) { result = true; break; } } return result;}//--------------------------------------------------------------------Widget::Visibility Widget::getVisibility() const{ return visibility;}void Widget::setVisibility(const Visibility newVisibility){ visibility = newVisibility;}//--------------------------------------------------------------------sf::Vector2i Widget::getScreenPos() const{ sf::Vector2i result(pos); if (parentPtr != NULL) { result += parentPtr->getScreenPos(); } return pos;}bool Widget::containsLocalPos(const sf::Vector2i &local) const{ sf::IntRect rect(0, 0, getWidth(), getHeight()); return rect.contains(local);}bool Widget::containsScreenPos(const sf::Vector2i &screen) const{ sf::IntRect rect(getScreenPos(), sf::Vector2i(getWidth(), getHeight())); return rect.contains(screen);}//--------------------------------------------------------------------Widget *Widget::mouseDown(const sf::Vector2i &mLocal){ Widget *resultPtr = NULL; if ((visibility == VISIBLE) && containsLocalPos(mLocal)) { for (WidgetIt it = children.begin(); it != children.end(); ++it) { resultPtr = (*it)->mouseDown(mLocal - (*it)->pos); if (resultPtr != NULL) { break; } } if (resultPtr == NULL) { resultPtr = this; mouseDownEvent(mLocal); } } return resultPtr;}//--------------------------------------------------------------------void Widget::update(){ if (visibility == VISIBLE) { doUpdate(); for (WidgetIt it = children.begin(); it != children.end(); ++it) { (*it)->update(); } }}void Widget::draw() const{ if (visibility == VISIBLE) { // Push (pos.x, pos.y) translation ... doDraw(); for (ConstWidgetIt it = children.begin(); it != children.end(); ++it) { (*it)->draw(); } // Pop translation ... }}
gui.h and gui.cpp
#ifndef GUI_HPP#define GUI_HPP#include #include using std::vector;class Widget;class GUI{ public: GUI() : focusPtr(NULL) {} virtual ~GUI() {} void clear(); bool pushWidget(Widget &w); bool removeWidget(const Widget &w); void clearFocus(); void setFocus(Widget &w); bool mouseDown(const int screenX, const int screenY); bool mouseMove(const int screenX, const int screenY, const int dx, const int dy); bool mouseUp(const int screenX, const int screenY); bool keyDown(const sf::Event::KeyEvent keyEvent); bool keyUp(const sf::Event::KeyEvent keyEvent); bool textInput(const sf::Event::TextEvent textEvent); void update(); void draw() const; private: vector widgetPtrStack; Widget *focusPtr; sf::Vector2i mouseToFocusPos( const int screenX, const int screenY) const;};#endif
#include "gui.h"#include #include "widget.h"using std::find;void GUI::clear(){ clearFocus(); widgetPtrStack.clear();}bool GUI::pushWidget(Widget &w){ bool result = false; // Only allow root widgets if (w.getParentPtr() == NULL) { removeWidget(w); widgetPtrStack.push_back(&w); if (w.isModal) { clearFocus(); } result = true; } return result;}bool GUI::removeWidget(const Widget &w){ bool result = false; WidgetIt it = find(widgetPtrStack.begin(), widgetPtrStack.end(), &w); if (it != widgetPtrStack.end()) { if (*it == focusPtr) { clearFocus(); } widgetPtrStack.erase(it); result = true; } return result;}void GUI::clearFocus(){ if (focusPtr != NULL) { focusPtr->loseFocus(); } focusPtr = NULL;}void GUI::setFocus(Widget &w){ if (focusPtr != &w) { clearFocus(); focusPtr = &w focusPtr->gainFocus(); }}//--------------------------------------------------------------------bool GUI::mouseDown(const int screenX, const int screenY){ bool result = false; for (WidgetRIt it = widgetPtrStack.rbegin(); it != widgetPtrStack.rend(); ++it) { sf::Vector2i mLocal = sf::Vector2i(screenX, screenY) - (*it)->pos; Widget *newFocus = (*it)->mouseDown(mLocal); if ((newFocus == NULL) && (*it)->isModal) { break; } else if (newFocus != NULL) { setFocus(*newFocus); result = true; break; } } if (!result) { clearFocus(); } return result;}bool GUI::mouseMove(const int screenX, const int screenY, const int dx, const int dy){ bool result = false; if (focusPtr != NULL) { focusPtr->mouseMove(mouseToFocusPos(screenX, screenY), sf::Vector2i(dx, dy)); result = true; } return result;}bool GUI::mouseUp(const int screenX, const int screenY){ bool result = false; if (focusPtr != NULL) { focusPtr->mouseUp( mouseToFocusPos(screenX, screenY) ); result = true; } return result;}sf::Vector2i GUI::mouseToFocusPos( const int screenX, const int screenY) const{ return sf::Vector2i(screenX, screenY) - focusPtr->getScreenPos();}//--------------------------------------------------------------------bool GUI::keyDown(const sf::Event::KeyEvent keyEvent){ bool result = false; if (focusPtr != NULL) { focusPtr->keyDown(keyEvent); result = true; } return result;}bool GUI::keyUp(const sf::Event::KeyEvent keyEvent){ bool result = false; if (focusPtr != NULL) { focusPtr->keyUp(keyEvent); result = true; } return result;}bool GUI::textInput(const sf::Event::TextEvent textEvent){ bool result = false; if (focusPtr != NULL) { focusPtr->textInput(textEvent); result = true; } return result;}//--------------------------------------------------------------------void GUI::update(){ for (WidgetIt it = widgetPtrStack.begin(); it != widgetPtrStack.end(); ++it) { (*it)->update(); }}void GUI::draw() const{ for (ConstWidgetIt it = widgetPtrStack.begin(); it != widgetPtrStack.end(); ++it) { (*it)->draw(); }}
This is my take on an ostensibly light-yet-flexible core of a GUI system.
Everything is a Widget. Widgets can contain other Widgets, making trees of Widgets.
Widget trees are handled by a GUI manager. A GUI manager takes a root Widget and feeds it input events, updates it, and renders it. A GUI manager actually handles a stack of root Widgets to represent overlapping UIs to represent things like modal forms and pop-up menus.
The GUI manager also keeps track of the current focus, needed for keyboard events. A Widget, wherever it is in its tree, gains focus when clicked on. Focus is lost when an empty space is clicked.
I need to finalize how I'm going to handle Widget sizes. You'd be allowed to set the dimensions of most Widgets, but some Widgets like icons may have fixed sizes. I'd also want default sizing behaviour for certain types of Widgets, like containers that grow and shrink to fit their child Widgets, that would end when you set a specific size on the Widget.
Widgets also need to notify their parent when their dimensions, and their visibility, change. I'll likely do this through a callback like childResized().
What's the difference between draw and doDraw? Instead of declaring ConstWidgetIt etc you could use the auto keyword. I use STL lists instead of vectors because a vector array has to be reallocated and each element reassigned each time an element is pushed.