Fomenko

GUI Programming

By Alexandre Chêne|August 06, 2026

This post is an overview of my choices when programming my own GUI library, this is not a tutorial.

It’s been a while since I wanted to own the GUI stack, despite having experience with making GUIs from scratch, I never had the time to properly pause what I was doing and be a bit serious with it. But for the past month or so, I was making exactly this, a GUI library, which is now what I use for my current game editor and what I’m gonna use for any forthcoming projects as well. And here’s what it looks like:

Some of these clips are taken from an upcoming game I am working on.

The main direction I gave myself was to speed up the velocity of writing UI code. I should be able to write UI easily, quickly and without having to think too much about states, or managing positions of UI elements. Another design point; because I will (potentially) use this code for many of my projects and for a long time, it’s important for me to be pleased while using the UI; so I want some kind of juicy effect or smooth transition while interacting with widgets.

So I headed writing what is commonly called an “immediate-mode” GUI system. This style of writing UI code matches my own style in general, it’s procedural and pretty natural to integrate with your own code logic. But there’s not a single way of implementing an immediate-mode UI system. You could design something which is fully stateless, and let the user code manage this part, but then the user has to manage more things, like positions and sizes of widgets, input cursor position, and more. A balance must be found, between what I put in the user’s hands from what I encapsulate in the GUI library, without constraining (too much) what UI we can build.

And one thing I noticed when working on my own game editor or other diverse projects; the majority of widgets could follow simple layout rules instead of having to manually assign positions and sizes to them. That’s why I implemented an internal auto layout system. A bunch of functions are exposed to the user in order to change on the fly the layout behaviors. Like to put your next widget on the same line as the previous one, or to take the full row, or start a column mode, etc. You can also escape this system by providing a rectangle (meaning a combo of position and size), or semi-escape it by just providing its sizes.

In terms of actually drawing the UI, I wanted something simple as well, no signed distance field for rounded corners or no complex shader to set. Instead, just a bunch of vertices to draw. It’s simple that way, and versatile enough to do everything I wanted. The price is a bit more vertices to draw, but it’s pretty anecdotic, plus I use some tricks to lower the vertex and draw call count, which I’ll describe later on.

Before explaining the core mechanism of my code, it’s important for me to state that I was inspired by many others. From Casey Muratori which is the first who formulated and brought to the public the concept of immediate-mode GUI, but also Omar’s Dear ImGui library which is probably the most famous implementation of an immediate-mode GUI. Less known because not yet public, but the Jai module ‘GetRect’ written by Jonathan Blow was very valuable to me, I particularly liked reading his code, there are some interesting tricks in it. Also, the whole UI post series written by Ryan Fleury was highly interesting, and I recommend you to read it as well.

Alright, let’s dig into my own implementation now!

Handling Inputs

One of the first things I had to do, was to get input events from the underlying operating system. Without that information I couldn't know mouse positions or if the user clicks somewhere.

I didn’t necessarily need all the input events. I needed the mouse positions and its click states (pressed or released). I also needed some specific key events to make keyboard navigation possible, like tab, escape or the return key. To make copy/paste possible, I needed modifier states as well (shift, ctrl, etc).

This library doesn’t know anything about the underlying platform, so by itself it cannot pull those input events. Instead, the user can use a bunch of exposed functions to pass io events to the library. So for the mouse positions, mouse wheel, or modifiers, there are set_mouse_position or set_modifiers. For key and click state events, I’ve a single enum mixing mouse and keyboard keys. To set them, it’s done with set_key_event function, where I map a key to its state. Here’s an example which loops over SDL events:

while SDL_PollEvent(*event) {
    if event.type == {
        case .SDL_KEYUP; #through;
        case .SDL_KEYDOWN;
            down := event.key.state == SDL_PRESSED;

            if event.key.keysym.sym == {
                case .SDLK_ESCAPE;      set_key_event(.ESCAPE, down);
                case .SDLK_BACKSPACE;   set_key_event(.BACKSPACE, down);
                case .SDLK_RETURN;      set_key_event(.RETURN, down);
                // ... Same for other keys.
            }
    }
}

As you can see, it’s straightforward here. Internally, key states are stored in a fixed array, where each slot represents a unique key state. And for each frame, I loop over the array to update or reset their flags.

Finally, in the code I do is_pressed(.MOUSE_LEFT) to know if some key is pressed or not, some examples:

  • For copying: is_pressed(.KEY_C) && modifiers & .CTRL.
  • For pasting: is_pressed(.KEY_V) && modifiers & .CTRL.
  • For character deletion: is_pressed(.BACKSPACE, repeat = true).

Widget Occlusion

Most of my widgets are attached to some window, which is a movable and resizable rectangle surface. And you could have multiple windows of course, which can be drawn on top of other windows. A widget who opens some kind of a menu will be drawn on top of other widgets too. I had to order draw elements then, but not only, I also had to disable interaction of occluded elements.

To answer those needs, every widget is associated with a layer. A window for example, is just a movable and resizable layer. A dropdown menu is also another layer. Each layer gets a serial number which defines its position into the layer stack, and each one of them holds a draw list. At the end of a frame, I sort all layers by their serial number in ascending order. So the one with the largest serial number will be drawn last, meaning on top of all of them. If I want to raise a window, I simply mutate its serial number to be the new highest.

So by sorting layers, I managed to draw them in the correct order. But I still had to disable interactions of occluded widgets. And it’s pretty simple in fact, to interact with a widget, I check if the mouse cursor position is inside the widget rectangle or not. Now, I add another check to know if the mouse is inside a layer with a bigger serial number than the widget’s attached layer; if true, it means that another layer is above.

Another thing to consider is the clipping area. When widgets go beyond their clipping region, I clip them on the GPU. But logically, I still process them. If a button is half clipped, the user should be able to interact with it still, but only on the part that is not clipped. How to do that? First, I keep track of clipping areas in the code, then and because I know upfront the layer rectangle, and the position of the mouse at the interaction moment; I simply check if the cursor is inside the clipping area or not.

Measurements

All space measurements like gaps between widgets or padding within them, or the scrollbar width, the row height and more, have to be known somehow. I could have hardcoded their value, but generally it’s better to let the user choose them. I could have used a default “styling” struct which would store this information, and the user would override it if needed. But then he would have to specify values for each widget type; it’s a hassle to change everything then.

So I decided that most measurements would be related to the text size instead. Everything is a percentage of this root value, for example the row height is 1.4 times the text size. That way, the user has to specify only one value.

And it works particularly well when it comes to managing scale factors; on Apple devices, it’s often 1 logical point equals 2 pixels, so the user can set the text size to be N * 2 and everything scale properly on Retina display.

Also, sometimes the user want to scale down or up the entire UI based on the application window size. And that’s pretty easy to achieve now; the user could simply based their text size on some ratio.

REF_HEIGHT :: 1000.0;
k := window_height / REF_HEIGHT;

set_theme(.{
	text_size = 18 * k * dpi_scale,
});

Text Processing

For this library, I decided to encapsulate the loading and parsing of font files. I’m not sure of my choice yet, and perhaps I’m gonna change that later. But otherwise, I would have needed the user to provide a function pointer to get the size of a particular text, or to get the character index based on some position. By encapsulating this machinery, all those vanished.

To parse font data and rasterize glyphs, I’m using the FreeType library. Each requested glyph data will be rasterized on the fly, but I still cache the glyph data in a lookup table, where keys are constructed from glyph unicode and its requested font size. When a new glyph is rasterized, and was not cached before, I update the font atlas texture, and notify the user. Accordingly, the user has to update the texture on the GPU.

// At init time.
font := bake_font("./cascadia_mono.ttf");

// Set loaded font as our UI font.
set_font(font, *font_atlas_texture);

// Main program loop.
while running {

    ui_begin();
    // Widgets...
    ui_end();

  // If some new characters were added to the font atlas, we update the GPU
  // texture to reflect that.
  if check_if_font_atlas_is_dirty_this_frame(font) {
    bitmap, width, height := get_raw_bitmap_texture(font);

    replace_texture_region(*font_atlas_texture, .{ 0, 0, 0 }, .{ width, height, 1 }, 0, 0, bitmap);
  }

  // Our code to render the GUI...
}

Here, font_atlas_texture is a texture loaded on the user GPU. By giving the pointer in set_font, the library can simply pass it later on, in the draw call loop.

Auto Layout

Placing items automatically can be tricky. Often you have to know all widget sizes upfront in order to properly place them. Expressing things like “We have 3 widgets on a row, the widget in the middle should stretch on what’s left” is possible only if the layout system knows there are 3 items, and at least the size of the first and last items.

Any UI could be transposed into a tree, and this is the right way to solve that. You build a tree of items, and at the end of the frame, you do a layout pass based on this tree. This tree is often reflected in the user API, where he’s constructing the shape of the tree by attaching nodes to parents, etc.

And I wanted something simpler, without having to construct a tree or postponing the layout pass. After all, it’s an immediate GUI, so the layout could be immediate as well!? Of course, this will have limitations, but I think I am fine with that, I can still fallback on manual assignments if I want something specific at some point.

So the core functionality of my layout system resides in one centralized function which allocates spaces, and increments a cursor for the next widget to be placed. Properties of the layout are stored in a “Layout” struct inside each layer. For some widgets, I rely on the previous frame's layout. For example, the total amount of space used by all widgets is known only after they are all placed at least once. So it means there is a one frame delay for some things like displaying the scrollbar, but I think that’s a fair tradeoff!

Here's what the struct looks like, so you can see how I keep track of the layout state across a frame:

UI_Layout :: struct {
    // The layout rect, relative to the layer rect.
    rect       : Rect;
    // The cursor we mutate, tracking where we are in the layout.
    cursor     : Vector2;
    // If columns value is above 0, it means we are in columns mode.
    columns        := 0;
    // Used to center widgets on a row, when previous widget height is bigger than the row height.
    row_max_height := 0.;

    // We compute after each alloc_space the total space our widgets takes
    // within its layout rect. Used notably for the scrollbar and gradient at
    // the bottom of the window.
    content_height : float;
    content_width  : float;

    // Set the scroll offset, this is set by the scrollbar widget.
    scroll         : Vector2;
    // Tracks which column we are now.
    curr_column        := 0;
    // Tracks previous widget size.
    prev_widget_size   : Vector2;

    // If we should place the next widget to the previous one.
    should_inline      := false;
    // If next widget should takes leftover width of the current row.
    should_fullwidth   := false;
    // If we should unforce a specific width to the next widget.
    should_force_width := 0.0;
};

Depending of the layout's state, I would increment the cursor in a particular axis or not. For example, if should_inline is set at true, I simply advance cursor.x to the previous widget width (plus gaps). If set at false, I advance cursor.y by the row height instead.

Here’s what it looks like in the user code:

label("text input:");
same_line();
if button("Click here!", .{ 200, -1 }) {
 log("Clicked.");
}

begin_column(2);
checkbox(*open_a, "Show Color Picker");
checkbox(*open_b, "Show Theme Picker");
checkbox(*open_c, "Show Undo/Redo");
checkbox(*open_d, "Translucent");
end_column();

set_width(0.3);
label("FPS:");
same_line();
full_width();
label("% ms", frame_time);

As you see, you don’t have to set positions and sizes. By default, each size is set to -1, which means the size is not set by the user, so I internally compute a default one based on the text size.

By default, a widget is always placed on the next row. But you can override this with the same_line function. This will set your widget next to the previous one (and centers it based on the height of the previous one as well!).

With set_width, you can also set the width of the next row, It’s useful, when you want to align items. There are two forms, if you set a float in the range of [0:1] I take a percent of the row width, so set_width(0.8), is 80% of the width. If you set a value bigger than 1.0, I use pixels instead.

full_width() means the next widget takes the width left of the current row. It works particularly well in combination of same_line. You can express things like “put this widget on the same row as the previous one, and take the leftover width of this row”. There’s a wrapper for this combo; field("Input", 300), can be useful!

There’s a column system as well, the layout will automatically keep track of the current column index, and assign widgets in its corresponding cell. The width of a cell is simply the layout row divided by the user’s given column number.

Persistent State & transitions

As said earlier, I wanted transition effect while using this GUI. When I hover a widget, I want to see smooth color changes, same when clicking on buttons or checkboxes. Since I wanted transition effects on each widget, I had to find a way to store and persist hovering or pressing durations of them.

To answer that, widget states are stored in a global lookup table, where the key is constructed by hashing the caller location (thanks to the #caller_location directive in Jai, but in C you would hash __FILE__ and __LINE__) and an optional string.

// By storing pointers to Widget_State base struct, we can store different
// state types into a single table.
widget_states : Table(UI_ID, *Widget_State);

Widget_State :: struct {
    // The constructed key based on caller location.
    widget_id : UI_ID;
    kind    : Type;
    rect    : Rect;
    status  : Widget_Status;

    press_duration  := 0.;
    hover_duration  := 0.;
};

Dropdown_State :: struct {
    // The base state struct.
    #as using base : Widget_State;

    open            : bool;
    want_search     : bool;
    search          : string;
};

Text_Input_State :: struct {
    // The base state struct.
    #as using base: Widget_State;

    text               : string;
    editing            : bool;
    texting_start_time : float;
    cursor_start  := 0;
    cursor_end    := 0;
};

// Then each widget retrieve their state with this call.
get_widget_state :: ($T: Type, id: UI_ID) -> *T {
    state, new := find_or_add(*widget_states, id);

    if new {
        ptr              := New(T, initialized = true);
        state.*           = cast(*Widget_State) ptr;
        state.*.kind      = T;
        state.*.widget_id = id;
    }

    return cast(*T) state.*;
}

One thing to notice tho; when the widget is inside a loop, the caller location does not differ, resulting in id collisions. To answer this, I added a stack of “contextual ids”, which the user can push and then pop. So when a contextual id is set, I use it to hash the key.

for 0..200 {
	push_id(it_index);
	defer pop_id();

	button("Button");
}

Optimizing Draw Calls

The draw call count can easily explode and I wanted to optimize that. Each time I draw a widget, I push vertices into the current layer’s vertex list. But not every vertex can be drawn in one draw call, they have to share two more things: the texture pointer and the clip region. When a button is draw, I draw one quad for the button shape, and for the label I draw one quad per character. Let’s say they are two buttons, it will look like that:

WIDGET A
- 1 quad without texture
- N quads with font atlas texture

WIDGET B
- 1 quad without texture
- N quads with font atlas texture

Here, because the texture pointer changes between widgets, from null to the font atlas texture, I create a draw call per widget. And with a complex UI, this can increase the draw call count a lot!

So what I’m using instead is pretty simple; the null texture becomes a 1x1 white pixel stored in the font atlas texture. So when I want to set a “null” texture, I still use the font atlas texture, but with the 1x1 coordinates instead. No more switching between texture pointers. Thanks to this trick, I save draw calls and even simplify the UI shader by removing the condition if a texture is set or not.

Another thing optimized: I don’t process widgets’ vertices when its rectangle is fully outside its clip region. It’s only a single widget rect versus clip rect check, and it saves a lot of draw calls and vertex count.


As I said at the beginning of the post, I’m using this library for my own game editor now, and it works pretty well. It’s not fully finished still, and soon I’m gonna add new widgets, like a color wheel, sliders, and a widget to make node graphs (to edit shader parameters). I will continue to iterate over and over, so stay tuned!

Also I decided to open source the full code of my GUI as well, available here. It should be pretty straightforward to integrate this module into Jai codebases, but I do believe it serves better by actually being a source of information and knowledge for those who want to make their own! So you can read the code and make your own sauce!

–––
Don't hesitate to reach out on bluesky or via twitter.