Skip to content
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

Calculating a UI's size without rendering it #606

Open
mankinskin opened this issue Aug 3, 2021 · 10 comments
Open

Calculating a UI's size without rendering it #606

mankinskin opened this issue Aug 3, 2021 · 10 comments
Labels
design Some architectual design work needed layout Related to widget layout

Comments

@mankinskin
Copy link
Contributor

mankinskin commented Aug 3, 2021

It would be cool if we were able to get a widgets size before actually rendering it. I know this is a fundamental limitation of immediate mode UIs, but it should be easily possible to do this, right? Basically we just make all of the calls except the ones that take any effect on the screen.

My situation is that I have a row of items with different sizes, and I want the entire row to have the same height. When I add the tallest item first, this works, because then the later elements know their height. But when the tallest element is not the first, then the first item will not know how tall it should be. So I would like to iterate through all elements before rendering them, to calculate their sizes and find the max height, then set use that height for all elements when rendering them.

Screenshot_2021-08-04_00-41-16

The idea is basically to render to an offscreen buffer, just to get the final size of the Ui, without actually rendering anything. If there is some way to know if the Ui changed it would also be possible to cache something here, but I doubt that this will be possible without compromising developer experience.

@mankinskin
Copy link
Contributor Author

mankinskin commented Aug 3, 2021

Maybe this could be implemented in Painter by painting to something like a "null layer", which just means don't do any rendering?

egui/egui/src/painter.rs

Lines 12 to 32 in 784bac5

/// Helper to paint shapes and text to a specific region on a specific layer.
///
/// All coordinates are screen coordinates in the unit points (one point can consist of many physical pixels).
#[derive(Clone)]
pub struct Painter {
/// Source of fonts and destination of shapes
ctx: CtxRef,
/// Where we paint
layer_id: LayerId,
paint_list: std::sync::Arc<Mutex<PaintList>>,
/// Everything painted in this `Painter` will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
clip_rect: Rect,
/// If set, all shapes will have their colors modified to be closer to this.
/// This is used to implement grayed out interfaces.
fade_to_color: Option<Color32>,
}

or maybe just by having an empty clip rect?

@mankinskin
Copy link
Contributor Author

mankinskin commented Aug 3, 2021

Perfect! This seems to suffice, at least for vertical and horizontal layouts:

fn measure_pattern(&self, ui: &mut Ui, pat: &PatternVis, graph: &GraphVis) -> Response {
    let old_clip_rect = ui.clip_rect();
    let old_cursor = ui.cursor();
    ui.set_clip_rect(Rect::NOTHING);
    let r = self.pattern(ui, pat, graph, None);
    ui.set_clip_rect(old_clip_rect);
    ui.set_cursor(old_cursor);
    r
}

I.e. before measuring we cache the Placer cursor and the Painter clip rect, then we set the clip rect empty, so nothing is drawn, then we do the draw call, take the response, and re-set the cursor and clip rect to the old values for the actual draw.

Now we can take the rect.height() from the Response and adjust the layout our elements:

Screenshot_2021-08-04_01-28-08

I would like to make this a supported Api in Ui something like fn measure<R>(&mut self, add_children: Fn(&mut Ui) -> R) -> Response or maybe something like this returning a Rect. But first I will have to get this to work with the Grid layout.

@emilk
Copy link
Owner

emilk commented Aug 15, 2021

Having something like this would be useful, but it can quickly lead to an exponential explosion of runtime. If N nested ui functions all call measure, then the innermost closure will be called 2^N times.

An alternative that I have been thinking about is to have an optional pre-pass, where we call all the apps Ui code once without any paining, and with a special "this is a prepass" flag in the Context. This will allow widgets to store sizes which can be recalled in the second and final pass. This requires a bit of design work though.

@mankinskin mankinskin mentioned this issue Aug 29, 2021
11 tasks
@emilk emilk added the design Some architectual design work needed label Sep 30, 2021
@DanielJoyce
Copy link

Couldn't the ones already calculated memoize their dimensions though? Doesn't avoid the 2^N but does prevent recalc. Also, isn't N simply the depth of the tree? So in most cases it won't be 'terrible'

@mankinskin
Copy link
Contributor Author

mankinskin commented Oct 11, 2021

@DanielJoyce the problem is that you don't know when you would have to recalc. You could store the rect from the previous measurement, but I don't think we have a way of knowing if the size could be different this frame. So my idea would be to just let the user handle when to measure his Uis. We could still cache the last measured sizes, but the user will have to make sure they can use them, and this might not work so well.

So it would be nice if we could detect changes somehow if we are going to cache things..

Edit: oh, I am just now putting your comment into context.. yes, this would probably work well, for the measurements of a single frame, we should definitely cache sizes and reuse them, then empty all caches for the next frame. Maybe later this resetting can be disabled for Uis we know didn't change.

@emilk
Copy link
Owner

emilk commented Oct 25, 2021

I wrote down my thoughts on a two-pass solution to this problem in #843

Many of the concerns there apply equally to the "measure" approach.

@follower
Copy link
Contributor

follower commented Nov 7, 2021

without any paining,

I definitely support this approach. :D

@Rechdan
Copy link

Rechdan commented May 23, 2023

In the current version I don't see ui.set_cursor, was that removed?

@ktnlvr
Copy link

ktnlvr commented Jul 12, 2023

+1, don't see no ui.set_cursor.

@mankinskin
Copy link
Contributor Author

mankinskin commented Jul 12, 2023

It's there but it's a crate private function

pub(crate) fn set_cursor(&mut self, cursor: Rect) {

I made a fork and added it to Ui's public interface to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Some architectual design work needed layout Related to widget layout
Projects
None yet
Development

No branches or pull requests

6 participants