-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improving layouting #4378
Comments
I recently also ran into this while trying to fix #3074. One other possible way, at least where it's an issue of translations, is to fake everything being fine by fixing it afterwards. This should work when it's happening while the mouse is already occupied doing, like dragging windows around, since there wouldn't be any way to notice the response being 1 frame delayed. Unfortunately it falls apart for anything more complex, or when it is possible to do two things at once like with touch controls, or when there are massive differences between the estimated and actual positions. That does beg the question, how is correct interactivity vs visuals weighed? I think more people will notice when visuals are delayed by a frame, while noticing a lagging response is harder due to them being invisible. Is that an acceptable tradeoff, and what other methods like translations and hiding windows for an initial frame are available when it is? Edit 2: Leaving some of this here but removing a lot of my rambling. The more I think on this, the more it's turning into a worse version of multi-pass immediate mode, so probably the wrong direction if not applied carefully. |
Perhaps a mix of immedieate and pre layouted mode would be a good idea? Perhaps implement a widget that acts as a container for pre layouted widgets. Those widgets still get drawn on the screen every frame, but cannot change their bounding box during a frame. These pre layouted widgets could request a resize to be performed before the next redraw. You could still do things like change color on hover or highlight a button this way during draw. For those pre layouted containers you could implement some more advanced layouts aswell as provide a trait users could implement to provide their own layouting. A big con is however these pre layouted components would probably require a completely different api. Sizing information would probably need to be made available in a dedicated method. Id especially pay attention to text sizes as those are a bitch in every other framework that does pre layouting. The draw method would need to be told its bounding box and perhaps steps should even be taken to prevent drawing outside of the bounding box. Adding/Useing immedeate widgets inside of pre layouted components/Containers should be possible without much issue if you assign a bounding box with a size known to the layouter to them. I think many people will fine pre layouting for some use cases more intuitive than others. Allowing users to explicitly combine them with immedieate widgets would probably be ideal, at least in my opinion. |
Sadly we can't center sliders atm with egui. References: emilk/egui#1144 emilk/egui#4378
I experimented a bit with a new |
Item B is a very close match for how React useRef hooks work, and the problems solved by 'react hooks' in general are a close match for various issues found in immediate mode UI rendering. |
Understanding the problem
One of the main drawback of single-pass immediate mode GUIs is layouting. There is a cyclical dependency between position, size, and layout. This can be illustrated explained by an example:
Say you want to show a small dialog window in the center of the screen. To position the window we must first know the size of it. To know the size of it we must first layout the contents of the window. In immediate mode, the layout code also checks for interaction ("was the OK button clicked?"), thus we need to know where to place the OK button before doing the layout. So we need to know where to put the window before we can layout the contents of the window, but only after laying out the contents of the window can we know the size of the window, and thus where to place it so that it is centered on the screen.
The crux of the issue is really knowing the size of a thing before you can lay it out properly. So, how can we know the size of a thing before calling the layout code for it?
There are simple atomic widgets like
egui::Button
that calculates its size based on the text it contains and the border. This is where egui "bottoms out". These atomic widgets can be properly centered, because they only result in a single call toui.allocate*
.Whenever you have a group of widgets though, you have a problem.
Figuring out sizes before layout
So how can we know the size of a thing before doing the layout?
There are a few different ways:
A) Using fixed size elements (e.g. decide that the dialog window is always exactly 300x200 no matter what it contains)
B) Remember sizes of elements from previous frames (first frame calculate sizes, all other frames: position things based on those sizes). egui uses this strategy for some stuff (like
Window
,Grid
,Table
, …)C) An API for calculating the size of an element before adding it (#606 - has potential for exponential slowdowns)
D) Multi-pass immediate mode (rejected)
I think B) is the most interesting one to dig deeper into.
Whenever the B) strategy is used, one should be careful not to show the widget during the "layout" frame. For instance, a centered
egui::Window
is invisible for the first frame.The B) strategy also fails if the thing changes size over time. Though it is self correcting, it has a frame-delay. So in the original example: if the dialog windows grows it will shift to the right for one frame, then re-center then next frame. This will look ugly.
Improving layout given sizes
Once you have the size you should be able to apply any advanced layouting technique. For instance using:
egui_extras::Strip
Here we have a lot of opportunity for improvement in egui. As a start, we should at least write good examples for all of these.
The text was updated successfully, but these errors were encountered: