Immediate mode GUI

Recently I have been re-writing my own GUI library. This time I took an immediate mode approach rather than the traditionally “retained mode”. I am quite happy with what I’ve got so far, both in terms of functionality and aesthetic. The following is a screenshot.

One of the most welcoming feature of immediate mode GUI is it’s ease of use. For example, with EDXGui, to add dialogs the same as the screenshot above, all the code that’s required are below. The structure of the dialog matches with your code intuitively and the layout is auto-generated.

	EDXGui::BeginFrame();
	EDXGui::BeginDialog();

	EDXGui::Text("Right docked dialog");

	static string buf("");
	EDXGui::Text(buf.c_str());
	if (EDXGui::Button("Button 1"))
		buf = "Button 1 clicked";
	if (EDXGui::Button("Button 2"))
		buf = "Button 2 clicked";

	static float counter1 = 0;
	static int counter2 = 0;

	//EDXGui::Text("Value 1: %f", counter1);
	EDXGui::Slider<float>("Float slider", &counter1, 0.0f, 20.0f);
	//EDXGui::Text("Value 2: %i", counter2);
	EDXGui::Slider<int>("Int slider", &counter2, 0, 5);

	static bool show = false;
	if (EDXGui::CollapsingHeader("Collapsing Header", show))
	{
		static bool checked = false;
		EDXGui::CheckBox("Check Box", checked);

		static int radioVal;
		EDXGui::RadioButton("Radio Button 1", 1, radioVal);
		EDXGui::RadioButton("Radio Button 2", 2, radioVal);
		EDXGui::RadioButton("Radio Button 3", 3, radioVal);

		static bool show2 = true;
		if (EDXGui::CollapsingHeader("Collapsing Header 2", show2))
		{
			EDXGui::Button("Hidden Button");
			EDXGui::CloseHeaderSection();
		}

		EDXGui::CloseHeaderSection();
	}

	static int selected = 0;
	ComboBoxItem items[] = {
			{ 1, "Item 1" },
			{ 2, "Item 2" },
			{ 3, "Item 3" },
	};
	EDXGui::ComboBox("Combo box", items, 3, selected);


	static string textBuf1("Text field 1");
	static string textBuf2("Text field 2");
	EDXGui::InputText(textBuf1);
	EDXGui::InputText(textBuf2);

	static int digitInput;
	EDXGui::InputDigit(digitInput, "Digit input");

	static bool showSecondDialog = true;
	EDXGui::CheckBox("Multiple Dialog", showSecondDialog);

	static bool showConsole = true;
	EDXGui::CheckBox("Show Console", showConsole);

	EDXGui::EndDialog();

	if (showSecondDialog)
	{
		EDXGui::BeginDialog(LayoutStrategy::Floating);
		{
			EDXGui::Text("Multiple dialogs supported");
			static string multiLineText = "Multiple lines of texts and wrapped texts supported:\n";

			if (EDXGui::Button("Add Texts"))
				multiLineText += "Adding more texts! ";

			static float scroller = 0.0f;
			static int contentHeight = 0;
			EDXGui::BeginScrollableArea(320, contentHeight, scroller);

			EDXGui::MultilineText(multiLineText.c_str());

			EDXGui::EndScrollableArea(320, contentHeight, scroller);
		}
		EDXGui::EndDialog();
	}

	if (showConsole)
		EDXGui::Console("", 300);

	EDXGui::EndFrame();

All the tedious work of layout, writing callback events, listeners, data copying and so on, all of which you would have to handle yourself using retained GUI, are handled by the library. EDXGui provides a number of widgets including buttons, sliders, check boxes, radio buttons, and also a number of more complicated ones such as combo boxes, fully featured text/digit input fields, multiple-line texts auto formatting and automatically generated scrollers. Addtionally, it supports multiple dialogs with different layout strategy.

IMGUI is more suitable for applications that re-paint the client area every frame, such as games or CAD apps. Compared with the more traditional Retained Mode GUI (RMGUI) such as Qt, MFC, IMGUI is simpler both in terms of usage and implementation. I have written both and I feel that IMGUI is easier for first-timers who want to create their own GUI library.

I will briefly talk about the differences between RMGUI and IMGUI both in using them and actually implementing them. Starting with using the GUI library, RMGUI typitically requires the user to explicitly initialize every control it has. Every control is an entity that lives in the RAM somewhere and each one holds certain states/data, for instance a button might hold a function delegate for the event it triggers, a slider might have a current value. Addtionally, users need to pay the extra attention copying data to/from the GUI (MVC mode). A typical usage pattern of RMGUI might look like this:

image alt text

IMGUI will appears to be much more “brutal force”. Widges don’t have their own objects in memory, and they are all stateless. Users don’t need to worry about the data transfer between GUI and the app, don’t even need to write a callback function. Each widget is implemented as a function, which is called directly in the Draw() function of the app. The same GUI written with RMGUI would look like this if using IMGUI:

image alt text

In terms of implementing the library, in RMGUI each widget has their own class, which has members to store corresponding data. These classes all implement certain interfaces for IO handling, rendering, data transfering and so on. There is also a GUI manager like class that manages all the widgets, dispatches events and render widgets. In c++ it might look like this:

image alt text

The logic is indeed a little convoluted here, compared with IMGUI, in which each widget is simply a giant function that handles both IO interaction and rendering. They are also stateless, all states are stored in the app itself. The code might look like this:

image alt text

The code of IMGUI mode tend to be much shorter but more intensive, every thing is packed in a single function. In my own EDXGui, which has a fully featured text input field, there are only hundreds lines of code. Since programmers don’t need to deal with writing callbacks and data transfers seperatly, it’s actually simpler to implement.

IMGUI also has its own demerits. IMO animation is awkward to support in IMGUI mode, since widgets are stateless and animations would require widgets to at least keep track of time. It sure can be done with your own extra data structure, just much less intuitive than RMGUI. Some also argue that executing UI logic of every widget is wasteful. This might indeed be a concern when implemented poorly (consider re-formatting a multi-million lines string every frame).

However in practice I found most of the problems have little impact. The OnGUI of Unity Engine actually uses IMGUI mode. So it’s definitely usable in real projects.

Lastly, a good tutorial can be found here: www.iki.fi/sol

Tags: ,

Updated:

Leave a Comment