handmade.network » Forums » Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
sharazam
9 posts
#16696 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks, 1 day ago Edited by sharazam on Nov. 5, 2018, 3:10 a.m. Reason: Initial post

Hi. I've been following Handmade Hero for a few years and over the past year I've been working on my GUI framework for Rust called Azul (https://azul.rs/). While it's not entirely finished or even 0.1 ready yet, it's at least in a useable state (i.e you can use it to make UIs although it might not be the best idea). Last week, someone posted it to social media (a bit early, but whatever), which caused it to gain a bit of "popularity" - and I got a few people telling me that I azul "isn't IMGUI because it uses callbacks". So I wanted to ask - is it still IMGUI or not? Would you consider it "immediate mode" or have I invented something else? And what are specific marks that you could recognize an "IMGUI" framework by, i.e. if these things are at least present, then the framework is IMGUI.

I personally argue that Azul fits the description of "IMGUI", or is at least closely related - to give a short description of what azul does differently from dear imgui or other frameworks, here is a short description of how a window is rendered:

  1. The user defines a data definition in which to store every data of the application.
  2. The user defines a layout() function which is basically the rendering loop - however, it doesn't render directly in a display list like dear imgui, but rather into a DOM-like tree. The DOM tree stores function pointers to callback functions.
  3. The user initializes the data model with the initial state
  4. When the app is started and the "game loop" is entered, three steps occur until the window is closed:
    1. The layout() function is called and returns the DOM.
    2. The (immutable) CSS is applied to the DOM and it does the layout and rendering
    3. If there were any rectangles hit, the function pointers are invoked with a pointer to the application data model (so the callbacks can change the data model).



That's the overall picture. And I argue that it's similar to IMGUI because it's really only different in two aspects:

  • Instead of doing layout, styling and business logic in one function and rendering into a display list (i.e. `render_rectangle(0, 0, 100, 200)`, they are seperate functions - the DOM is an "immediate rendering target", then the styling and layout is done by the framework.
  • Yes, azul uses callbacks, but that doesn't necessarily need to clash with the IMGUI paradigm. The problem with retained-mode, traditional OOP GUIs is that they duplicate the UI state and the actual application state, leading to synchronization problems. Any callback is directly tied to the "button" object, in Azul a "button" is simply a function that, given a certain state, renders a DOM element (with a certain CSS id and class, so it can be styled). The button itself doesn't retain state. Azul re-renders everything on every frame (like dear imgui) and gives every callback access to every state (dear imgui uses static mutable data here, or function-local data).


The only exception to the last point is the way that azul does two-way data binding using stack-checked pointers, however, there is no "syncronization" going on either, all that happens is that Azul invokes certain "default functions" with a void* to valid stack data. These default callbacks can directly change the data, there is no large "synchronization" going on like in retained-mode frameworks. Azul does not retain any information from the previous frame (except for optimizations for more efficient caching, but not in a retained-mode way). So I wanted to ask if this is still "immediate mode" for you or if there's some specific definition of IMGUI.

The thing is, it's not MVC, because there is no "controller" - there is a model and a view, but there is no "controller" (which, in retained-mode frameworks usually does all the synchronization between UI and data model, at least in theory). It's also not MVVM, because there is no "ViewModel" or whatever you want to call it. So if it isn't immediate mode, then what is it?
mmozeiko
Mārtiņš Možeiko
1826 posts / 1 project
#16697 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks, 1 day ago

IMO because you are storing DOM tree and callbacks which is user UI specific thing inside your library data structures it is not really IMGUI. It may look like one from API point of view, but internally it is not. The point of IMGUI is that it does not maintain extra state (except some boilerplate like keyboard/mouse input, etc).
sharazam
9 posts
#16699 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks, 1 day ago Edited by sharazam on Nov. 5, 2018, 5:36 p.m.

But I am not storing the DOM tree, that's the point. The DOM tree gets thrown away at the end of each frame. So you can return a new DOM tree for every frame with different callbacks, different UI structures.
mmozeiko
Mārtiņš Možeiko
1826 posts / 1 project
#16700 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks, 1 day ago Edited by Mārtiņš Možeiko on Nov. 5, 2018, 6:57 p.m.

Yes, I understand that. But you are still storing DOM during the frame. That is the hidden state. Can application modify it? Can you modify/add/remove GUI elements in the callbacks? I assume no, and users needs to adjust their applications to do the GUI things in your layout() function. Thus - no immediate mode.
sharazam
9 posts
#16701 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks ago

But you are still storing DOM during the frame. That is the hidden state.


Okay, but if that is your definition of IMGUI, then dear imgui also has hidden "state" and isn't IMGUI - in dear imgui you don't render into a DOM, but you also don't render directly to the screen. dear imgui "records" things using a global context. For example take a list:

1
2
3
4
ImGui::BeginChild("Scrolling");
for (int n = 0; n < 50; n++)
    ImGui::Text("%04d: Some text", n);
ImGui::EndChild();


Here the "BeginChild" and "EndChild" modify a global state, so that dear imgui can track whether or not things like scrollbars need to be rendered. And there is no way around that - somehow that has to be tracked. So there is still state, but it's just global instead of being passed around (like in Azul in the DOM struct). So there is a difference in the implementation, but not in the paradigm:

1
2
3
4
5
let mut dom = Dom::new(NodeType::Div);
for n in 0..50 {
   dom.add_child(NodeType::Label(format!("{}: Some text", n));
}
return dom;


The only thing here is that there is no global state touched anywhere, but that state is rather recorded in the DOM and the layout happens in a separate step rather than as-the-values-are-inserted. And where is the state stored in dear imgui for whether a button is hovered or clicked or what the value of a float is?

1
ImGui::SliderFloat("float", &my_float, 0.0f, 1.0f);


Here the "state" is in the &my_float, - and in Azul this my_float variable would simply be moved in the data model, then you could also take a pointer to it. So there has to be some state - dear imgui just puts the state in either static mutable data, global variables or in function-local data while Azul puts it into the apps data model. "So, users need to adjust their applications if they want to store whether a button was clicked - thus no immediate mode", using that logic, the same thing can be said for dear imgui. The state for the button being in an on or off state has to be stored somewhere and all that Azul does is move it from global state into a struct.

1
if (ImGui::MenuItem("Close", "Ctrl+W"))  { my_tool_active = false; }


- here "my_tooL_active" is a function-local variable, in Azul, this is simply put in the data model instead of being function-local or global state:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct MyApp { my_tool_active: bool }

impl Layout for MyApp {
    fn layout(&self) -> Dom<Self> {
        MenuItem::new("Close", "Ctrl+W").with_callback(On::Click, Callback(toggle_tool))
    }
}

fn toggle_tool(state: &mut MyApp) -> UpdateScreen {
    state.my_tool_active = false;
    UpdateScreen::Redraw
}


You could technically write this as:

1
2
3
4
5
6
7
struct MyApp { my_tool_active: bool }

impl Layout for MyApp {
    fn layout(&self) -> Dom<Self> {
        MenuItem::new("Close", "Ctrl+W").on_click(|app| app.my_tool_active = false; )
    }
}


... if the semantics of Rust would allow this (since function pointers can't be defined inline) - but whether the `my_tooL_active = false` is inlined or in a separate function doesn't really change the paradigm. All that it does is modify the `my_tool_active` in the app state instead of it being part of the layout() function itself.

And yes, you have to "stringify" the results of your data model in the DOM - because there is no way to render a float directly in a UI, you have to convert it to a string, and then to glyphs. But dear imgui does the same, it has to - converting the floats to strings to render them in the UI happens in the ImGui::Text() - there the "state" of the float is copied and converted into a string.

For me, what makes a framework IMGUI or not is whether it forces the application to "read back" values from the UI, because then you get into the synchronization problems that IMGUI wanted to avoid - i.e. there are many UIs where you have to call things like "button.getText()" or something like that - because the state of the UI of what text that button has is now duplicated: The UI (owned by the framework) has a copy of the text, and the actual application code has its own copy and you, the application programmer, need to synchronize between the two. And IMGUI just said: Okay, to avoid this, we'll just redraw every button and everything on every frame, so there is only one value and the whole screen is re-rendered the whole time.

In Azul as well as in dear imgui there is only one copy of the buttons text - the thing in your application state is the "source of truth" and in can be directly modified without jumping through Observable<T> hoops. However, whether you draw into a display list or into a DOM or if you define functions as seperate callbacks or inline them in your layout() function is an implementation difference, not a paradigm difference.
mmozeiko
Mārtiņš Možeiko
1826 posts / 1 project
#16702 Is my GUI framework actuall IMGUI or not? - Debate about the definition of "IMGUI" / "immediate mode"
2 weeks ago Edited by Mārtiņš Možeiko on Nov. 6, 2018, 6:41 a.m.

By state I don't mean that imgui cannot have any internal data needed to layout elements. Of course it needs it. But that is imgui state, not collection of what elements user will need to have (application UI state).

Word "immediate" in imgui does not necessary mean that rendering happens immediately to screen. Afaik it means that you can get state of UI immediately not later whenever UI framework decides to deliver it to you. Thus user has full control and driving how UI events will be processed ("onButtonPressed") and is not relying on UI framework internals - this includes event loops, callbacks.

For me, what makes a framework IMGUI or not is whether it forces the application to "read back" values from the UI


IMHO callbacks are just very fancy way how to read back values from the UI framework.
As for values you are setting from code, that is trivial to do with non-imgui code - just set label/buttons/text-fields to values you want from your code on every frame update, and there won't be need for button.getText() calls.