Fomenko

GUI Programming

By Alexandre Chêne|August 04, 2026

It’s been a while since I wanted to own the GUI stack, despite having experiences 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:

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 point of design, I wanted something with a “feeling” when interacting with widgets, like smooth transitions for example.

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

One thing I noticed when working on my own game editor and 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 function are exposed to the user in order for him 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 and 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 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 it.

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

Widget occlusion

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

To answer those needs, every widget is associated with a layer. A window for exemple, is just a movable and resizable layer. A dropdown menu is also another layer. Each layer get a serial number which define its position into the layer stack, and each one of them hold a draw list. At the end of a frame, we sort all layer 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 we want to raise a window, we simply mutate its serial number to be the new highest.

So by sorting layers, we managed to draw them in the correct order. But we still have to disable interactions of occluded widgets. And it’s pretty simple in act, to interact with a widget, we check if the mouse cursor position is inside the widget rectangle or not. Now, we 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 goes beyond their clipping region, we clip them on the GPU. But logically, we still process them. If a button is half clipped, we should be able to interact with it still, but only on the part that is not clipped. How to do that? First, we keep track of clipping areas in the code, then and because we know upfront the layer rectangle, and the position of the mouse at the interaction moment; we simply check if the cursor is inside the clipping area or not.

Measurements

All gaps, paddings, row heights measurements are related somehow to the text size provided by the user. So everything is a percentage of this root value, for exemple the row height is 1.4 times the text size. 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 we 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 its text size on some ratio.

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

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

Auto layout

Making a full fledged layout system can be complex to implement, and I wanted something rather simple here. Plus, I prefer to rely on manual assignments if I want something specific. And actually, we can do lot of things without constructing a double linked-list, or postponing the layout pass. Tho, with my implementation there are limitations as well, expressing things like “We have 3 widgets on a row, the widget in the middle should stretch on what’s left” are not really possible because we have to know upfront how much widgets lays on this row to compute the proper placement. With a double linked-list and a postpone layout pass, it’s doable, as nicely described by Ryan Fleury there. But the design of the building code has to reflect this tree construction as well, and I didn’t want that.

The core functionality of my layout system resides into one centralized function which allocate spaces, and increment a cursor for the next widget to be place. Properties of the layout are stored into a “Layout” struct inside each layer. For some property, we rely on the previous frame. For exemple, the total amount of space used by all our widgets is known only after being they are all placed at least once. So it means we have one frame delay, but that’s okay I think.

Here’s what it looks like, and bellow I will describe a bit more about those functions:

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, we don’t have to set positions and sizes. By default, each sizes are set to -1, which means the size is not set by the user, so we internally compute a default one based on the text size provided by the user.

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.

With set_width, you can also set the width of the next row, It’s useful, when you want to align items. There’s two forms, if you set a float in the range of [0:1] we takes 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, we 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. We can express things like “put this widget on the same row as the previous one, and takes 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 tracks 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.

This GUI library is pretty stateless, but we need some persistent informations as well. For transition effects or the scrollbar position.

Each widget states are stored into a lookup table, where the key is constructed by hashing the caller location (thanks to #caller_location directive in Jai) and an optional string. But when the widget is inside a loop, the caller location does not differ, resulting into id collision. To answer this, I added a stack of “contextual id”, which the user can push and then pop.

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

	button("Button");
}

Tricks to optimize draw calls

The draw call count can easily explode and we want to optimize that. Each time we draw a widget, we push vertices into the current layer vertex list. But not every vertices can be drawn into one draw call, they have to share two more things: the texture pointer and the clip region. When we draw a button, we draw one quad for the button shape, and for the label we draw one quad per character. Let’s say we have two buttons, it will looks 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 change between widgets, from null to the font atlas texture, we create a draw call per widget. And with a complex UI, this can increase a lot the draw call count!

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

Another thing I optimized: we don’t process widgets outside its clip region. It’s only one condition (widget rectangle vs clip rectangle) and can save lot of draw calls and vertex count as well.


As I said at the beginning of the post, I’m using this library for my own game editor now. It’s not fully finish yet, and I will iterate over and over, adding new widgets and more, depending of my own experiences and journey. Nonetheless, I decided to open sourced it, available here. It should be pretty straightforward to integrate this module in Jai codebases, but it’s also cool to read the code, and see how X or Y are implemented!

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