This is the first post in a series of posts in which I’ll write a bit about the design/tech details of GimGui, my work in progress game GUI library. I you haven’t yet, you can check out the previous post Introducing GimGui for an introduction on it.

The element

In the GimGui data model, gim::Element is the most basic building block that constitutes the GUI. In fact, any GUI consists only of gim::Elements and nothing else. These store various attributes (they can for example store position and size) and are tied together in a parent<->children tree structure, much like the DOM tree in web development.

GimGui parenting

So far this is nothing unusual. However, most other solutions I’ve looked into from this point start to utilise inheritance to derive from the element class and define various attributes/features through that. I early on decided to not take this approach - in GimGui there are no derivatives, there are only Gim::Elements. This is mainly due to two reasons. Firstly, I like separating data from logic and having elements solely as general data containers is a neat way of doing this. Secondly, I got inspired from entity component systems that work around the idea that what defines a game entity in terms of attributes and behaviour can be assembled on the fly from basic building blocks. I figured if it works well for entity systems, surely it would work similarly well for GUIs since there is not really any huge fundamental differences between the two.

Another thing I decided to do to keep things simple was to not hide these basic elements behind some managing structure, such as if I’d have some kind of ElementManager which would be responsible for creating elements and managing their lifetime. Instead, elements are just on their own and the ownership is straightforward: Children are owned solely by their direct parent and the root parent of a tree is just like any instance of gim::Element. gim::Element itself is fully RAII so the user doesn’t have to care about any managing. Here follows an example on how a GUI can be created.

As you can see, elements can be constructed out of initialiser lists of r-values of elements, which makes it rather simple to construct a GUI structure inline. This single root variable is now our GUI and can be rendered or otherwise operated upon.

//declare the root variable that can later be used and will
//contain the children
gim::Element root({"root"}, {},
{
    gim::Element({"container"}, {},  //first child element
    {
        gim::Element({"text"})  //grandchild
    }),
    gim::Element({"container"}, {},  //second child element
    {
        gim::Element({"text"}),  //grandchild
        gim::Element({"button"}),  //grandchild
        gim::Element({"button"})  //grandchild
    })
});

//children can also be added after initialisation
gim::Element additionalChild({"extra_child"});
root.append(std::move(additionalChild));

Attributes for storing generic data

As stated in the introductionary post, I want to make as few hard assumptions as possible about the end use case. This poses an issue - what data should the elements be capable of storing? Position and size comes to mind as obvious candidates, but what more? It seems reasonable to support texturing elements, as well as having text on them. But then what about probably-needed extra attributes controlling the wrapping/stretching of the texture? Or the size of the text? Also, how should users handle custom data like ‘health’ for a healthbar or ‘blinking rate’ for a blinking blob element? This is complicated to decide without having either too much or too little, and in either case it could end up messy storing it all in different member variables.

To avoid these decisions, I chose the most generic of all approaches - elements have no hard coded attributes at all. Instead they store a map of names mapped to any class objects that can store any datatype. This is very flexible and lets the user store whatever data they need without having to hack around anything. The biggest drawback with this IMO is the loss of compile time type safety. I compensate to an extent by using asserts which will notify on failure in runtime, and I think the drawback is outweighed by the clean code that follows. We can now add attributes to elements like this.

gim::Element element({"example_element"},
{
//here follows a list of name-value pairs to initialize the attributes.
//the values can be of any type.
    {"position", Vec2(23, 10)},
    {"size", Vec2(64, 64)},
    {"name", "Health meter"s},
    {"health", 100},
}, {/*children go here*/});

//attributes can be added afterwards
element.createAttribute("color", Color(255, 255, 0));

//we can now get attributes...
std::cout << element.getAttribute<std::string>("name");
//outputs: "Health meter"

//...and set them
element.setAttribute("health", 56);

//elements with all their children and attributes can
//even be deep copied very easily
gim::Element copy = element;

Combining this inline creation of attributes with the inline creation of children makes a quite powerful way of defining whole GUI trees with children and attributes in place in a rather straight-forward (almost JSON-esque) syntax. Trying to set/get inexisting attributes or setting an attribute to the wrong type will cause assertion errors.

Labels

You might have noticed from the earlier code snippets that strings like “container”, “root” and “example_element” were added in the constructors. These texts are labels and every element can have zero, one or more of them. The gim::Element class also comes with a couple of ways to traverse the tree, and also search the tree using the labels. This is easily shown with a code snippet.

gim::Element element(/* some initialization*/);

//returns a pointer to the direct parent - nullptr if no parent is present
element.parent();

//returns a list of all direct child elements
element.children();

//returns a list of pointers to all direct children with
//the supplied labels
element.findChildren({"label1", "label2", "label3"});
//does the same as the above, except recursively
element.recursiveFindChildren({"label1", "label2", "label3"});

//returns a list of pointers to any direct or indirect parent
//with the supplied labels
element.findParents({"label1", "label2", "label3"});

This functionality will probably later on be separated from the Element class.

Convenience is important

At this point, maybe you are thinking, “sure, this is very generic - but for the typical user it is insane to have to work at this low-level and define all the common attributes for every element.” This is a fair thought and is indeed not a good thing for a library aiming to be simple to use. Worry not - remember that this is the most basic component of the library, so it is bound to be low-level. GimGui will though come with higher level tools for dealing with creating/populating/traversing/rendering/etc elements. For example, if we want to be able to create buttons easily, we can leave the element in the tree with no attributes and only a “button” label, then let a populator object traverse the tree and add all required attributes for buttons on every element with the “button” label. This can of course be combined in various ways. By providing tools like this to cover all typical use cases, the library will stay easy to use.

Work in progress

Keep in mind that GimGui is right now a work in progress so the part of the API depicted here might undergo change.

Also feel free to leave feedback on whatever you might have an opinion on - positive or negative! :) After all, I do wish for the library to eventually be useful for more people than just me.