Retained Mode GUIs (RMGUIs)
If you've done any GUI programming before, it's almost certain that you've used a Retained Mode GUI. Be it MFC, GTK, Qt, Swing or CEGUI, they all have the same programming model:
- Create some widgets
- Link them in parent->child relationships
- Setup a few tons of callbacks
- Let the GUI run
- The GUI executes user code from within callbacks
This is the retained mode and is similar in function to retained mode renderers - you create the scene and let something manage it continuously.
There are reasons that this programming model is the dominant one for most purposes:
- It allows for lazy updates
- Events trigger partial redraws and callback execution
- Most applications have very static interfaces - their data changes but model doesn't - thus the GUI widgets may be created once and then just updated with data
- The setup code can be generated by a WYSIWYG editor, while the user simply defines callback actions.
- Widgets are self-contained and don't require external state to function
While RMGUIs have advantages, they also come with some drawbacks:
- The GUI pretty much takes over all execution
- It's hard to exchange the widgets in-place while the GUI is running - it's all optimized towards the GUI being static and data changing from time to time.
- RMGUI editors usually produce code, so the app has to be recompiled to try even the simplest of changes.
- When not using a WYSIWYG editor, the programmer has to create tons of code for initialization, callbacks and destruction
For these reasons, some game/non-database programmers became weary with RMGUIs and sought alternatives.
Immediate Mode GUIs (IMGUIs)
This is where IMGUIs enter the area. While in RMGUIs you had to create stuff, with IMGUIs you simply use stuff. The idea is to be able to write code like:
while (true) {
renderScene();
updateLogic();
if (doButton(SOME_ID, ....)) {
perform some actions;
}
}
The original approach is to make the doButton functions render the button immediately and return some useful info, such as whether the button was clicked. The doButton function is called each frame and communicates with some stored GUI context. That one can store some shared info. For instance, a click event means pushing the mouse button and releasing it over the same widget. Thus, the GUI context stores the ID of the widget which was under the mouse button push. doButton can then look for button releases and compare state, in order to reason whether the widget was clicked.
IMGUIs have numerous advantages:
- More or less zero storage per widget
- The GUI can be completely dynamic
- Code in just one place
- No need to use signals & slots
- User code controls the GUI, not the other way around
But it as usual, IMGUIs are a compromise:
- The user is responsible for retaining GUI state
- It doesn't yield itself to lazy updates
- Fancy predefined layouts are a no-go, because widgets can't be iterated-over
- Some info about widgets can only be discovered with a delay of one frame
- There are issues with keeping track of the widget IDs
A more recent approach to IMGUIs is to keep the API in immediate-mode and letting the backend cache state about widgets. This removes some of the disadvantages of 'pure' IMGUIs. Pushing the concept a bit further, we arrive at ...
RMGUI + IMGUI = Hybrid
The Hybrid GUI is an attempt to take the best of immediate and retained mode approaches. The API is mostly immediate mode, yet state is retained automatically and there are ways to create widgets to stay. It is one of the possible compromises between the two programming models, yet one that has numerous advantages:
- The GUI can be completely dynamic
- No separation of code into init, handling, destruction
- No need to use signals & slots
- User code controls the GUI, not the other way around
- It allows for lazy updates
- GUIs can be easily defined in config files
- Widgets are self-contained and don't require external state to function
- Automatic ID generation
- Clean and intuitive API thanks to some advanced D features
The most serious drawback of Hybrid is that it's tricky to do lazy state updates with it. In order for it to have an immediate-mode API, some code has to be executed once per frame (once per mouse move / keyboard press in the best case). So far, lazy updating remains a distant option and not a top priority, but to some extent, it should be possible in the long run.
Reference