Readit News logoReadit News
SeanAnderson · 2 years ago
I cannot recommend immediate mode GUI programming based on the limitations I've experienced working with egui (https://github.com/emilk/egui) in Rust.

egui does not support putting two widgets in the center of the screen: https://github.com/emilk/egui/issues/3211

It's really easy to get started with immediate mode and bust out some simple UIs, but the second you start trying to involve dynamically sized elements and responsive layouts -- abandon all hope. The fact it has to calculate everything in a single pass makes these things hard/impossible. Coming from a strong CSS/React background I find the limitation maddening.

... that said, I'm still using it to build a prototype UI for https://ant.care/ (https://github.com/MeoMix/symbiants) because it's the best thing I've found so far.

I'm crossing my fingers that Bevy's UI story (or Kayak https://github.com/StarArawn/kayak_ui) becomes significantly more fleshed out sooner rather than later. Bevy 0.13 should have lots more in this area though (https://github.com/bevyengine/bevy/discussions/9538)

slmjkdbtl · 2 years ago
It sounds like limitations of egui / Rust instead of immediate mode GUI. I've made flexbox-like layout systems in immediate mode GUIs and found them far easier than retained mode, since everything is redrawn every frame.
SeanAnderson · 2 years ago
That's fair. I don't have experience with other immediate mode libraries. It's good to hear that it's not an intrinsic limitation

https://github.com/emilk/egui?tab=readme-ov-file#layout Here the author discusses the issue directly. They note that there are solutions to the issue, but that they all come with (in their opinion) significant drawbacks.

For my use case, if I have to do a lot of manual work to achieve what I consider behavior that should be handled by the framework, then I don't find that compelling and am inclined to use a retained mode implementation.

bollu · 2 years ago
Could you link to your code? I am interested in the API design of meshing a layout system with immediate mode GUIs
lucasmerlin · 2 years ago
I'm using egui to build an app with a mobile ui and I'm really enjoying it so far. The main reason I chose egui is because I need tight integration with wgpu and this is really seamless with egui.

In the process of building my app I have also created a couple of crates for egui that add drag and drop sorting, infinite scroll and other utilities.

In the example showcasing my crates I also try to show that you can make a pretty ui with complex layouts using egui (check the gallery and chat example): https://lucasmerlin.github.io/hello_egui/

I've had to spend a lot of time improving egui and it's ecosystem in the process of building my app but it seems to be worth it.

If you're not building a graphical app it probably makes more sense to use something like tauri or flutter as the gui to build a cross platform app with rust, at least until it's gui ecosystem matures.

at_compile_time · 2 years ago
I've dabbled with egui, and ran into this limitation almost immediately (har har).

I got around it by storing widget size between frames so that I could center it properly on the next frame. Not perfect, but it worked.

_eojb · 2 years ago
None of these limitations have anything to do with an imgui frontend api though.
SeanAnderson · 2 years ago
Can you elaborate? I'm not sure I understand. To me, these limitations feel intrinsic to immediate mode.
kevingadd · 2 years ago
It's best used for developer tools or simple UIs that don't have complex layout constraints.

For what it's worth, I'm building all of my game's UI using a pseudo-imgui framework, but I've had to do manual layout in specific places, and I updated the layout engine to run a second pass in specific cases (dynamically sized elements, primarily when text is being auto-wrapped to fit available space). This sort of stuff is only possible when you control things end-to-end.

In practice even these IMGUI frameworks don't generally do their layout in "one pass", it just appears to be a single pass to you. oui and its derivative layout.h both do a size calculation pass and then an arranging pass, for example. I originally used layout.h's algorithm, but eventually designed a new one which operates in ~3 passes:

* 1. Calculate minimum sizes and, for containers with multiple rows/columns, construct 'runs' of sequential boxes in a given row/column.

* 2a. For containers with wrapping enabled, scan over runs and when we find one that's too big for its container's available space, split controls from one run into a new one.

* 2b. For containers with children, scan through their children and grow any children that are meant to expand into available space. (You can't do this until you've measured everything and wrapped.)

* 2c. For any containers where you wrapped controls or expanded controls, recalculate their size and throw out the result from 1, since wrapping/expanding potentially changes their size.

* 2d. For simplicity you can introduce an alternative version of pass 2 for 'grid layout', where children all have fixed sizes and predictable wrapping. I've only started doing this recently, but it's great for things like listboxes and dataviews. If you do this, you don't need to do the multiple subpasses of 2a/2b/2c, and arranging becomes easier.

* 3. Now that you've measured everything and distributed boxes into rows/columns, you can scan through each 'run' and put every control into its place, applying things like centering and RTL/LTR modes.

I do think "it should be possible to efficiently perform layout for your whole UI from scratch every frame" is a good principle, it pressures you to architect your application in a cleaner, less fragile way. But sometimes you really want to retain stuff, like shaped glyphs for big blocks of unicode text, etc. Right now my game runs over 120FPS on a terrible laptop from 2015 and around 800FPS on my 3-year-old workstation, but a major portion of the CPU time is all spent doing layout. That's not great.

empiricus · 2 years ago
Did you profile the layout code? How many UI elements do you display normally? And what is the O complexity of the layout algorithm above? My intuition is that even if it looks like a lot of code, it should be incredibly fast for at least hundreds of elements.
xvedejas · 2 years ago
Have you looked into slint? I'm curious to hear other Rust devs' experience with it.
SeanAnderson · 2 years ago
I haven't. I was just searching for a GUI library that was Bevy-compatible and slint isn't at the moment: https://github.com/slint-ui/slint/discussions/940

Sorry!

Deleted Comment

Fire-Dragon-DoL · 2 years ago
There was a post long time ago here on HN about a PhD that discovered immediate mode gui would be more efficient than the existing paradigm, but that ship has sailed
perlclutcher · 2 years ago
I don't think primitive layout is necessarily a limitation of the immediate mode GUI paradigm. It just requires layout to be deferred until the end of the frame. And of course to do that performantly, you'd likely need some caching between frames.

But here's the kicker: egui already does cache things between frames—for accessibility support, it already builds a full retained widget tree! From there it's not a huge jump to cache layout too. I really wish someone would experiment with this idea. Maybe Gio will be the ones to do it.

Nevermark · 2 years ago
The baseline difference seems to be the difference between organizing GUI elements as stored method+field entities, vs. dynamically combined function+argument entities.

It is a lot like the difference between greedy and lazy execution.

Which code style is best depends on the structure of the items it is applied to.

--

With 100 different views, respectively over 100 different items to be viewed, storing 100 view+item pairs as objects is both general and efficient.

With 10 different views, repeatedly over 10 different items to be viewed, there are still 100 interface elements. But repeatedly joining view and item information at draw time provides a 10x space/object savings. And allows for other code simplifications.

--

General takeaway: Pre-compiled general libraries, that cannot be treated as inlined compile-time optimizable templates (in the code context where the templates are applied), will be inefficient and overbuilt for cases with more structure and simplicity than they were designed for.

crq-yml · 2 years ago
I see it as the crossover between approaches amenable to static order and iteration, vs those that demand constraint optimization.

This is a recurring problem in programming, because if you code towards the optimal result, you end up making a static, linearized computation that doesn't need further configuration. But if your goal is to provide interfaces and automation, you are tasked with a constraint problem, where you have multiple potential solutions and you either have to filter them down to a single unambiguous result, or find a heuristic that defines the "best one".

The problem occurs with type systems, graphical layout, multi-body physics simulation, dependency management and a host of other things. I consider it the most unaddressed subject in CS because it's so relevant to applications and yet industry continually reinvents it in terms of a bespoken algorithm for that one application.

And depending on what you're doing, you end up biasing to one or the other method first: a small, well-defined problem only needs the computer to do "brute" things, while a problem dependent on the computer managing the complexity of the problem needs it to find and correct errors.

eru · 2 years ago
Wouldn't link-time-optimization take care of your 'general takeaway'?
Nevermark · 2 years ago
Yes, definitely. Link, optimize, compile/code-gen.

Instead of optimize, compile, link.

moron4hire · 2 years ago
Immediate mode GUI is fine for quick and dirty things, but once you start dealing with multiple application views that conditionally show/hide, with branching flows of any kind, you'll find yourself on a hardline track to reinvent retained mode GUI from scratch just so you can handle the event loop in a sane way.

Object oriented, event driven widgets composed into more colored views are really, really good for developing UI. There's a reason every major OS GUI toolkit is this design. If you componentize like you are supposed to--instead of just smashing everything into one form--they work and can be reasoned over with few surprises.

The trouble comes from not componentizing things that you should when your current platform doesn't provide the full menagerie of widgets that you'll need. Basically, it you have any kind of input that results in a value that is not just a raw string, you should be building a component for that input: URLs, paths to files, numbers, dates, selectors for picking a set list of structured objects, etc.

jesse__ · 2 years ago
I'm sorry, but I've got to call this out as woefully inaccurate.

> [...] so you can handle the event loop in a sane way.

The whole point of an immediate-mode framework is that the call-stack acts as the event loop. You run through your UI code, and if a thing was interacted with on the previous frame, you just handle it. What about that setup is not sane?

> [...] instead of just smashing everything into one form [...]

There is nothing about immediate mode that makes it more or less able to have everything 'smashed into one form'. You can do that just as well with your retained-mode framework of choice.

> The trouble comes from not componentizing things

This also has nothing to do with the immediate vs. retained mode discussion. You could just as easily make a giant mess in retained-mode by not making reusable components.

> once you start dealing with multiple application views that conditionally show/hide, with branching flows of any kind [..]

In my spare time, I work on a game engine whose editor/debug UI is completely immediate mode. There are discrete views into nearly everything in the engine. Entity data, a color picker, memory and CPU performance views, asset viewers/pickers, a terrain editor with like 10 modes.. you name it. There are plenty of conditionally shown views. There is plenty of 'componentizing' of things going on. The editor UI is in the neighborhood of 10k lines, most of which is generated. If that's below your bar for quick-n-dirty, maybe we have different opinions on what qualifies.

HelloNurse · 2 years ago
> You could just as easily make a giant mess in retained-mode by not making reusable components.

Classic example: the Swing library for Java has layout managers, which help panels decide where their children go, and the most "powerful" one is the GridBagLayout, roughly equivalent to HTML tables with extra features and a favorite of GUI builders.

It can be used to make an unmaintainable monster layout of most of a complex window in a single panel, to structure a trivial reusable component that would be easier to write with a simpler layout manager, or to try and design intermediate level components that are self-contained and benefit from general layout configuration, with no guidance at all from the library or from tools.

moron4hire · 2 years ago
>> The editor UI is in the neighborhood of 10k lines, most of which is generated.

I mean, this sounds like you've created a retained mode DSL that gets compiled into immediate mode.

And yes, 10 KLoC is definitely a small project, especially when "most of which is generated".

flohofwoe · 2 years ago
Isn't the whole point of immediate mode UIs to get rid of the "event loop" though?

> multiple application views that conditionally show/hide

The ImGui way of doing this is to conditionally run or not run the code which describes the conditionally shown UI elements. E.g. a simple

    if (viewShown) {
        ...code which describes what the view looks like
    }
There are plenty of real-world applications with complex UIs implemented in Dear ImGui which don't seem to have a problem with those things, e.g. see https://github.com/ocornut/imgui/labels/gallery

> The trouble comes from not componentizing things...

In ImGui, reusable UI components are just code which describes the UI component by calling lower-level ImGui functions and which itself is a new ImGui-style function. It works surprisingly well.

epcoa · 2 years ago
If your entire system ran with immediate mode GUIs your performance would either be in the toilet or battery life would be destroyed (or both).

They’re great for games and GPU where you’re in a for a pound anyway. There are some music apps that use it and it’s horrible if you’re on a laptop. No I don’t want to consume a couple watts when I should be idling because the entire screen is being repainted doing nothing. Makes electron seem nice.

moron4hire · 2 years ago
>> Isn't the whole point of immediate UIs to get rid of the "event loop" though?

No, not at all. It's to take explicit control of the event loop. It's right there, you have a loop and the buttons still fire events, you just have to check for them on every iteration of the loop.

Deleted Comment

jcranmer · 2 years ago
The way I think about it, GUIs come down to a few basic principles:

1. Program data is primarily hierarchical. This means that you can generally compose a view for a data item out of smaller views down to a basic set of elements (e.g., text display, combo box, etc.), and also means that you can generally ignore all data not included that view. There are some cases where this breaks down (tables really stretch it, for example), but it definitely holds for most GUIs.

2. UI also has state independent of program data. Immediate GUIs to a degree go ha-ha-there's-no-such-thing and make it somebody else's problem, but this state has to be retained somewhere. However, it's not exactly a binary choice between UI and program state--something like whether or not a checkbox is checked can go either way (I think of it as UI state because I tend to deal in batch-mode programs, where what is going to happen is the UI state will be distilled into the input to code I execute whose results will be displayed).

3. Conversion of program state to UI needs to be lazy--you don't want to forcibly map every element in a list immediately to UI widgets. The best example of this, of course, is the scrollbar.

4. Whereas program state is hierarchical, UI state is far less hierarchical in nature. That is to say, the state of a UI element may influence the display of a UI element in a completely different tree.

The difference between immediate and retained mode is less important than it might seem at first glance. If your UI is simple and largely static, hierarchical application of canned elements, then both an immediate and a retained mode interface will end up looking roughly the same in terms of code. If your UI is complex and deals with heavy and very impure state, it again doesn't matter all that much, because you're going to have to maintain that state all yourself anyways.

As a programmer who hates writing GUI code, though, all I really want is a thoroughly complete set of widgets (the ontology of UI is pretty standard, after all). I don't really care about immediate mode or retained mode, I just want a path that lets me easily render my program state with the minimum hassle. And quite frankly, it seems like the only GUI toolkits that make it as far as figuring out how to include a lazy table widget are the retained mode GUIs, which maybe suggests something about immediate mode GUIs.

merb · 2 years ago
> There's a reason every major OS GUI toolkit is this design.

Ehh.. the latest os ui toolkit is uwp which lost to react native (a im gui) and it does not look that winui3 will fix that. Linux has two major gui libraries, both are probably only used by enthusiasts and not by enterprises which prefer to target react native (mostly for different reasons tough)

Drawing components won because the object oriented world of gui design sucks and because it’s hard to target multiple systems. All modern ui libs use a kind of immediate mode. It’s way easier to understand and you do not need to explain why you need to use mvvm.

rstat1 · 2 years ago
For desktop use, Qt is quite widely used pretty much everywhere. I only ever see React Native being used so a company can claim to have a "native" mobile app.
jayd16 · 2 years ago
React native is an imgui? Aren't views defined with a declarative language similar but distinct from html?
c-smile · 2 years ago
Each tool is good for particular tasks it was designed for.

That's why my Sciter [1] supports both - as retained mode (DOM/CSS) as immediate mode (element.paintXXX()).

Consider the task of marking some div as resizable - with eight square handles [2]. With immediate mode drawing that task is trivial:

   const div = ...;
   
   // draw sizing handles:
   div.paintForeground = function(gfx) {
     gfx.fillColor = "#0f0";
     for(let r of this.sizingHandlePlaces()) 
       gfx.fillRect(r); 
   }
otherwise, if we have only retained mode as in browsers, we will need to modify the DOM heavily and create temporary elements for handles.

[1] https://sciter.com [2] https://stackoverflow.com/questions/20984181/how-do-i-make-a...

flohofwoe · 2 years ago
TBH from looking at the code examples it looks a bit like it's halfway stuck between a traditional object-oriented UI framework, and a "proper" immediate mode UI (for instance why is there a variable that seems to be a button object).
hsn915 · 2 years ago
Yea, it's unfortunate.

I also find their layout "framework"/API to be weird and counter intuitive.

jbverschoor · 2 years ago
Immediate mode is very common in game development. Super easy to reason about and debug.
bartwr · 2 years ago
I'm a former game dev and I used ImGui a lot and don't think it's used because those reasons.

It's used for quickly hacked debug tools to interleave UI and regular logic and not do a logic/view separation (as it would result in code bloat and a necessity for a refactor). You want UI code do some logic (like modifying properties of some game entity or renderer) and prefer to inline it. Lots of game code is effectively YOLO without even a single test. It's also typically guarded by IfDefs and compiled out of release versions.

But as soon as it stops being just hacky debuggers and people try to write proper tools in it, it becomes much more of a pain - people try to (poorly) emulate a retained mode in it, hold state, cache - and it becomes unreadable mess.

ocornut · 2 years ago
> But as soon as it stops being just hacky debuggers and people try to write proper tools in it, it becomes much more of a pain - people try to (poorly) emulate a retained mode in it, hold state, cache - and it becomes unreadable mess.

Effectively people are hasty and don't spend the time to try doing things nicely, in particular because the first steps and debug use allow you to do quick things.

But I don't think it's a fundamental property of IMGUI or Dear ImGui that "proper tools" become particularly more of a pain. Of course it is more work to make a proper tools than hasty-debug-tools, and I can easily see how underengineering can back-fire (over-engineering it likewise).

1ark · 2 years ago
This is such a good and clear take.
low_tech_punk · 2 years ago
I'm web dev recently introduced to game dev. I'm curious why the two worlds have such different approaches. Another example is ECS being more prevalent in games than in web apps.
nox101 · 2 years ago
because they're doing different things.

Web page: Wait for data from server, update page to match, this usually happens at most every few seconds. (or if it's server based) Fetch data, format into html, set to browser

Game: For 10s to 1000s of objects, run some code for each one at 60 frames a second. That code is usually one or more finite state machines and/or co-routines per object (or some hacked together code that effective does the same). This code updates a bunch of state for each object, and then other code displays the current state.

They're doing different things so they take different approaches.

PS: I get the above is over simplified.

kragen · 2 years ago
react is pretty common in web dev and it's immediate-mode

ecs is just watered-down in-ram relational databases, and relational databases are also pretty popular for web apps

jheriko · 2 years ago
i am a gamedev. i never encountered this until unity became popular, and even then it was always looked down on as a shitty and hacky approach to a well solved class of problem.

10 years after this, its still about the same... which is probably why an article like this has any controversy about it instead of being run-of-the-mill.

TillE · 2 years ago
Absolutely everyone (including the AAA studios) uses Dear ImGui for tools these days.

Retained mode is probably more common for user-facing GUIs, though.

dang · 2 years ago
Recent and related:

Dear ImGui: Graphical User Interface library for C++ - https://news.ycombinator.com/item?id=38710818 - Dec 2023 (109 comments)

refulgentis · 2 years ago
I'm seeing "immediate mode" recently, and I hadn't encountered it before. It is synonymous with a React / Flutter / SwiftUI approach to UI to my untrained eye. Help me fill in the rest?
flohofwoe · 2 years ago
The Dear ImGui readme is a good starting point:

https://github.com/ocornut/imgui

...now of course Dear ImGui is a specific implementation of the immediate mode UI philosophy, but the general ideas transfer to other imguis as well.

krapp · 2 years ago
There is also microui, which I like[0], it's much smaller but still gets the job done.

Which I forked to work with SDL2[1], no guarantees. It's fun to hack on.

[0]https://github.com/rxi/microui

[1]https://github.com/kennethrapp/microui

whstl · 2 years ago
It is not synonymous, but you are right that they are very similar concepts.

React's VDOM actually works just like immediate mode, updating it all on every "re-render" (with diffing taking care of updating only the parts of DOM that actually need an update). So it's a bit like "immediate on top of retained mode".

kragen · 2 years ago
popular imgui toolkits like dear imgui (fortunately, nobody yet argues that dear imgui isn't really imgui) also actually retain some per-widget state

i would say that the key question is whether widget deletion and updating is implicit or explicit; it's a question about how the api is designed, not how it's implemented

with immediate-mode graphics like <canvas> or windows gdi, if you update the screen and forget to visit a certain rectangle, that rectangle disappears from the screen. the same thing happens in an immediate-mode gui if you are drawing a window and forget to visit a certain checkbox. both whether it appears or not, and everything about how it's drawn, are guaranteed to be up-to-date with your

with retained-mode graphics like svg or tk canvas or current opengl, if you update the screen and forget to visit a certain rectangle, by contrast, that rectangle stays exactly the same as it was before. the same thing happens in a retained-mode gui if you are drawing a window and forget to visit a certain checkbox: the checkbox is displayed in the same way it was displayed before, and it may be outdated with respect to the program data it's nominally supposed to represent

omar's explainer at https://github.com/ocornut/imgui/wiki/About-the-IMGUI-paradi... pretty much agrees with the above, though he goes into a lot more detail. similarly casey's talking-at-the-camera video in which he popularized the concept https://youtu.be/Z1qyvQsjK5Y?t=6m30s

so it should be apparent that react falls solidly on the imgui side of the line, which is why people use it

okanat · 2 years ago
React maybe to a point but in general no.

Immediate means you get to decide (or even *forced*) how each and every frame gets drawn. Its opposite is retained GUI which means you have a set of data structures that are automagically drawn by the GUI library. Immediate GUI libraries work with functional-like types while retained GUI uses more data-oriented / object oriented types. Immediate mode is imperative, retained mode is declarative.

Immediate can offer very low level control and easier combination of various drawing interfaces. Wanna put a button on top of your game canvas? It is basically inserting more code / function calls that just draws the thing in an event loop. However you're again responsible for parsing input (helper functions exist of course) and directing things to correct place yourself.

In retained GUI you define you need a canvas such and such place and a button with red borders and a click() callback that gets called automatically. The GUI framework does all the routing and drawing for you. If it doesn't support putting a button over a canvas, you have to do 5x work to customize it and add a new component type.

Immediate mode seems the way to go isn't it? But now you have a huge event loop that you have to split and organize. It usually takes weeks to onboard newbies and you have to write everything yourself (with the help of the libraries). If you mess up the organization, you'll have to search what exact bit of code draws what. Refreshing the UI and looks will require many manual modifications rather than a simple stylesheet change. And immediate GUI has no caching implemented. You are responsible for implementing any such thing. Otherwise you'll learn about the thermal and power limits of the system very quickly.

At its very core all GUI is immediate. Because a GPU is still a processor. A very special one that's optimized for doing mostly branchless and mostly simple arithmetic but still a processor with a machine code and memory. So all retained GUI libraries have a immediate drawing core.

mondrian · 2 years ago
Immediate mode is a fuzzy concept, as witnessed by this writeup: https://github.com/ocornut/imgui/wiki/About-the-IMGUI-paradi...
alpaca128 · 2 years ago
Immediate mode means instead of changing the state of UI widgets individually, the entire UI is redrawn with the current state. Afaik this has slightly worse performance but can simplify the code because the UI itself doesn't hold any state.
flohofwoe · 2 years ago
> because the UI itself doesn't hold any state.

...which isn't actually true in most immediate mode UI frameworks. They absolutely do persist state between frames. That state is just on the other side of the API invisible from the API user.

'Immediate mode UI' is only an API design philosophy, it says nothing about what happens under the hood.

echelon · 2 years ago
In graphics programming, immediate mode is the legacy, manual way of drawing.

You'd have code that would "draw thing 1", move, then "draw thing 2".

Modern pipelines instead have you upload your vertex data to the GPU and write shader code that tells the system how to draw it. They become managed by the GPU and your code cares less about explicit drawing.

Another way to look at this is that "immediate mode" feels much more imperative than the modern asynchronous graphics pipeline. You tell the system to draw or render something, and it immediately does so.

This post has another good explanation with simple code:

https://stackoverflow.com/questions/6733934/what-does-immedi...

It's much easier to wrap your head around immediate mode though, and several tools with this imperative/immediate philosophy (such as imgui) are popular.

Edit:

Here's a fantastic comparison: https://cognitivewaves.wordpress.com/opengl-vbo-shader-vao/

(See "Immediate" vs the more modern code that follows.)

krapp · 2 years ago
Any decent immediate mode framework, including Dear ImGUI, has backends which just send quads to the GPU. The code is imperative but it just winds up filling a vertex buffer.
oasisaimlessly · 2 years ago
Despite both having "immediate mode" in the name, immediate mode OpenGL and immediate mode GUIs share very little in common. Pros/cons definitely don't carry over at all.
flohofwoe · 2 years ago
Apart from most of your comment being irrelevant for immediate mode UIs, the rest is also at best misleading, since in modern 3D-APIs draw commands are also issued from scratch each frame (if only to kick off GPU shader code).

The very early D3D versions actually had an optional 'retained mode API' in the mid-90's, but that quickly went the way of the Dodo.