5 hours ago, maltman said:
Can you elaborate on why IMGUI is poopoo and what are some alternatives you suggest?
The alternative is called "retained mode" - it's where you construct the GUI out of persistent instances (OOP objects, components, etc) in a data structure. It doesn't matter whether it's inheritance or composition that you use, both are retained mode if you create a hierarchy of objects in a data structure and have the rendering and user interaction code refer to the hierarchical data structure instead of immediate mode code describing the structure.
Instead of calling "if (Button("Label", width, height)) { OnClick(); }" multiple times every time the user moves the mouse / resizes the window / presses a keyboard key, you write "button = new Button("Label", width, height); Controls.Add(button); button.Click += OnClick();", and you handle mouse movement, layout, etc with separate functions that traverse only the portions of the widget hierarchy that needs to be traversed at the moment. For mouse movement events, you use the natural spatial partitioning that rectangular hierarchies provide. This is a C# syntax example, but the language does not matter.
IMGUI Problem 1: Associative book-keeping between the immediate-mode code and what the user sees.
IMGUI code typically involves extra work in re-associating the imperative-style code (such as "if (Button("Label")) { OnClick(); }) back to the interactive elements that the user sees (which must have a persistent identity of some sort in order to be interacted with across multiple "frames").
For example, if you have a scrolling panel with several textboxes on it, and the keyboard focus is given to one of those textboxes, the user expects that textbox to keep its focus even if they scroll the panel such that the other textboxes are no longer visible.
This also means that if you suddenly need a GUI where you dynamically add or remove UI elements (such as culling them when they're not being rendered like in the scrolling panel example), you have to jump through extra hoops to make sure that your "if (Button(...))" call in one instance is associated with the same button that you end up rendering.
In some cases, the extra bookkeeping prevents your code from executing efficiently...
IMGUI Problem 2: Performance issues due to wasteful execution of immediate-mode code.
The "if (Button(...))" pattern does multiple things: It causes a button to be rendered at the given point in the layout, it checks to see if the user has clicked on the rectangular region that the button layout occupies, and it affects the layout of subsequent elements.
Since the IMGUI internal workings needs to adapt to the possibility that your immediate mode code doesn't actually call the Button function this time through, it needs to recalculate the layout every pass through the immediate mode code (or at least be able to detect whether anything has changed). Depending on how many passes you need to make and how many widgets you have, this can add an immense amount of overhead.
For large scrolling panels, immediate mode code typically has no way to efficiently cull widgets which are off the screen due to the immediate mode code still needing to call each widget's function so that the correct association can be made between the widget that the user is interacting with and the immediate mode code which needs to handle user interaction for that widget.
IMGUI Problem 3: Changing properties of UI elements requires re-executing ALL of the immediate mode code which can affect the layout of the single UI element you're changing.
In retained mode, you can just say "button.Color = Color.Red;" any time you have a reference to the button instance. Internally, the .Color setter sets a flag telling the rendering code that it needs to refresh. In Immediate mode, you are forced to make this change in the code which calls the Button(...) function, either before it using global state affecting the next called function (which is bad code design), or by passing an argument to the Button function (which requires that you set up the arguments to pass to Button before calling it).
If you attempt to make this approach more flexible by encapsulating the Button call and property setup code in an OOP class or struct-and-function pair, you have just converted to using a retained mode wrapper on top of immediate mode. Just avoid this in the first place by using retained mode from the beginning.
Source: I have extensive experience using Unity's IMGUI system for complex in-Editor tool windows, and have to constantly work around all of these headaches because Unity's Editor code still has no way to let me write retained mode UI. Retained mode I've used include Unity's UGUI system (which unfortunately is only usable while the game is running, not in the Editor) and .Net's WinForms and WPF systems. The retained mode systems are far better for creating complex UI than IMGUI is. I haven't used any IMGUI systems other than Unity's, but I have never seen an article describing and implementation which works any differently or solves any of those problems.