In this article we'll implement the infrastructure of the element hierarchy.
Our user interfaces are going to be built out of elements. Elements range from things that the user can directly interact with like textboxes and buttons, to static things like labels and panels. The elements will be arranged in a hierarchy. Each window gets its own hierarchy, and the window itself will be the element at the root of its own hierarchy. Each element that isn't at the root has a single parent, and some possibly-zero number of ordered child elements. See the diagram below for an example.
Each element is a heap allocated structure, featuring the Element
header. This'll grow throughout the tutorial as we increase the capabilities and responsibilities of elements. But for now, we'll just get the very basics going.
typedef struct Element {
uint32_t flags; // First 16 bits are specific to the type of element (button, label, etc.). The higher order 16 bits are common to all elements.
uint32_t childCount; // The number of child elements.
struct Element *parent; // The parent element.
struct Element **children; // An array of pointers to the child elements.
struct Window *window; // The window at the root of the hierarchy.
void *cp; // Context pointer (for the user of the library).
} Element;
It should become clear why we want to have a hierarchy of elements as opposed to a flat list as the tutorial progresses.
Let us add the Element
header to the Window
structure.
typedef struct Window {
Element e;
uint32_t *bits;
int width, height;
...
};
There are two remaining steps. We need to create a ElementCreate
function that will allocate a new element and wire it into the hierarchy, and secondly we need to use this function in WindowCreate
.
There is not much to be done in ElementCreate
. It allocates space on the heap for the element, sets the various variables of the structure, and puts a pointer to the element in the parent element's list of children.
Element *ElementCreate(size_t bytes /* must be at least the size of the header! */, Element *parent, uint32_t flags) {
Element *element = (Element *) calloc(1, bytes);
element->flags = flags;
if (parent) {
element->window = parent->window;
element->parent = parent;
parent->childCount++;
parent->children = realloc(parent->children, sizeof(Element *) * parent->childCount);
parent->children[parent->childCount - 1] = element;
}
return element;
}
In WindowCreate
we replace Window *window = (Window *) calloc(1, sizeof(Window));
with Window *window = (Window *) ElementCreate(sizeof(Window), NULL, 0);
. We then set the window pointer manually with window->e.window = window;
. Every element keeps a pointer to the window at the top of the hierarchy. Of course, this pointer could be found by manually walking up the parent
pointers starting from any element, but it is convenient to have easy access to the window, so we provide this field.
That's all for this tutorial. But we're finally start to get into the interesting stuff! :) Make sure you step through the test usage code in the file below so you understand what's happening.