Developer postOriginal post (opens in a new tab)

[Blog] Almost Practical IMGUI

Gooier Still

In the previous installment, I suggested some viewing and reading material to acquaint oneself with Immediate Mode GUIs. Hopefully you’ve checked that out but, if not, the following should still be easy to follow. I really wanted to get into the meat of the SilverQuest Gaiden implementation this time but as I was writing it seemed like a good idea to provide a very simple example to build from. So here it is.

A Very Simple Example

This example is based entirely on the pseudocode given by Casey Muratori in his talk.

Below are the IMGUI properties defined in the IMGUI object. These are shared by every element in the GUI.

/// IMGUI Properties{    //  ID Tracking    ACTIVE = -1;    HOT = -1;}

ACTIVE and HOT can hold the value of an ID code unique to each GUI element or control. ACTIVE takes a value when a control is being clicked on. HOT takes a value when a control is being hovered over. A value of -1 is used to indicate that no ID has been set. These two properties are the full extent of state tracking in a canonical IMGUI design. This minimalism is part of what differentiate Immediate Mode from traditional Retained Mode GUIs.

Let’s take a look at a typical widget. This is a button with a text label. The function returns a boolean set to true if it has been clicked, false otherwise. The function is called with arguments to define the button’s unique id, label text, position, and size. This information isn’t retained anywhere, it’s just there for the duration of the function call. Again, this sets IMGUIs apart from traditional RMGUIs.


/// imgui_do_button(id,label,x,y,w,h)
{
var ID = argument0,
LABEL = argument1,
XPOS = argument2,
YPOS = argument3,
WIDTH = argument4,
HEIGHT = argument5;

var result = false;

if (ACTIVE == ID) {
if (MOUSERELEASED) {
if (HOT == ID) {
result = true;
}
ACTIVE = -1;
}
}
else if (HOT == ID) {
if (MOUSEPRESSED) {
ACTIVE = ID;
}
}
if (point_in_rectangle(MOUSEX, MOUSEY, XPOS, YPOS, XPOS+WIDTH, YPOS+HEIGHT)) {
HOT = ID;
}

draw_set_color(c_red);
draw_rectangle(XPOS, YPOS, XPOS+WIDTH, YPOS+HEIGHT, HOT != ID);
if (ACTIVE == ID) draw_set_color(c_white) else draw_set_color(c_black);
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
draw_text(XPOS+0.5*WIDTH, YPOS+0.5*HEIGHT, LABEL);

return result;
}

After setting the default return value to false, the first section of code sets the state of the GUI button. It references some global MOUSE variables: MOUSEX and MOUSEY is the pointer position; MOUSEPRESSED and MOUSERELEASED are mouse button states. I’ll explain this section of code in reverse order since it makes the state transitions a little easier to understand.

If the mouse is over the GUI button, it is made HOT. If the GUI button is HOT and the mouse button has been pressed, it is made ACTIVE. If the GUI button is ACTIVE and HOT and the mouse button has been released, the result returned will be true. This is a Windows-like GUI button behavior. If the GUI button is clicked on but the mouse is then moved away from the GUI button before the mouse button is released, the GUI button will act as if it were not clicked and it will be made inactive.

The second section draws the GUI button in different ways, depending on its state. The GUI button appears as a rectangle with a label centered within it. The rectangle is drawn in red. If the element is HOT, the rectangle is solid, otherwise it is just an outline. If the button is ACTIVE, the text inside will be drawn in white, otherwise it is black. The text itself is drawn in the center of the button. Of course this is very simple looking but the button display could animated or 3D or as fancy as you like.

Finally, the result is returned. A result of true means the GUI button was clicked.

So, how is this actually used? During every screen update of our application or game we do this.


if (imgui_do_button(0000, "Hello", 10, 10, 100, 20))
show_message("Hello World!");

if (imgui_do_button(0001, "Goodbye", 10, 40, 100, 20))
game_end();

if (imgui_do_button(0002, "Score: "+string(global.score), 10, 90, 100, 20)
global.score += 10;

And that’s it. Note that in the third button call, “live” data is passed to the button and is acted upon immediately if the button is used. As above, this informs the Immediate Mode name and concept. There is no information or state for the button to keep track of. There is no need to set this information or query it later. The GUI elements are not objects, there are simply function calls.

Virtually any common GUI component can be constructed using this same basic design philosophy. The advantage here is that everything is in one place. With a Retained Mode GUI, things tend to be split up into design, rendering, setters, getters, callbacks, and everything is very static, sterile, and compartmentalized. Decades of GUI inertia have conditioned us to believe this is a good idea.

B-b-but…

You may be thinking to yourself that it still looks a bit fiddly. I have to type in all those coordinates. And that could mean a lot of tedious iteration as elements are nudged around. And if I have to move a bunch of them — yikes! – that’s going to be a lot of work. And that can’t be better than using a GUI design tool to simply place widgets and drag them around the screen with a mouse, lickity-split, I’m done. And you’d be right. It’s not a magic bullet and it can’t solve every problem out-of-the-box.

HOWEVER there is also no reason you cannot extend this method to be more like a traditional RMGUI. That’s a strength, not a weakness. If you want to define your GUI with data and callbacks, go for it. Build a drawing tool for your layout or fashion an automatic layout engine, if that floats your boat. You can make it as complex as you want but it will never be more complex than it needs to be or the equivalent RMGUI. These are things I’ll be covering in the future.

Parting Shots

Developers often dismiss IMGUI as only useful for simple tasks and failing when it comes to dynamism, system scalability, or complex controls. This is demonstrably false. Take a look at this.

That’s Unity and that’s an Immediate Mode GUI.

Nicholas Francis, Unity Technologies co-founder and GUI programmer, gave a talk about it at GDC Europe 2009. You can listen to it here. Unfortunately it isn’t video and doesn’t have slides but it’s a valuable listen.

  • Immediate-Mode GUI in Practice, GDC Europe 2009

Johannes ‘johno’ Norneby has a book-in-progress where he gets into more sophisticated concepts for a hybrid MVC-IMGUI. You can read it here.

  • Immediate Mode Model/View/Controller

This GDNet discussion debates the merits of IMGUI versus RMGUI. Read at your peril.

  • GDNet Discussion: Designing a GUI system, hints appreciated.

And this is just over-the-top. Anti-OO zealotry? Really? As if OOP was something to aspire to. (runs for cover)

  • Why IMGUI arguments fall short

-Xot