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

Improve Area sizing with dynamic content #5138

Open
abey79 opened this issue Sep 20, 2024 · 3 comments
Open

Improve Area sizing with dynamic content #5138

abey79 opened this issue Sep 20, 2024 · 3 comments
Labels
bug Something is broken egui

Comments

@abey79
Copy link
Collaborator

abey79 commented Sep 20, 2024

Consider this example, which (maybe naively) tries to display a popup menu with content that can change over time:

use eframe::egui;

fn main() -> eframe::Result {
    env_logger::init();

    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
        ..Default::default()
    };

    let mut flag = false;
    let mut row_count = 5;

    eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.label(format!("Showing {} rows", row_count));
            if egui::menu::menu_button(ui, "BTN", |ui| {
                ui.radio_value(&mut flag, false, "False");
                ui.radio_value(&mut flag, true, "True");

                if flag {
                    egui::ScrollArea::vertical().show(ui, |ui| {
                        for _ in 0..row_count {
                            ui.add_space(30.0);
                            ui.label("Veeeeeeeeeeeery long text.");
                        }
                    });
                }
            })
            .response
            .clicked()
            {
                if row_count % 2 == 1 {
                    row_count -= 3;
                } else {
                    row_count += 5;
                }
            }
        });
    })
}

It results in something along these lines:

Export-1726816792556.mp4

The popup menu/scroll bar combination needs a way to react (entire automatically or at least by way of an API) to the change of content size and resize if appropriate.

@abey79 abey79 added bug Something is broken egui labels Sep 20, 2024
Repository owner deleted a comment Sep 20, 2024
@abey79
Copy link
Collaborator Author

abey79 commented Sep 20, 2024

This diff proposed by @emilk improves the situation a bit. However, it means the the text will no longer wrap should its length grow, which can be an issue.

  egui::menu::menu_button(ui, "BTN", |ui| {
+     ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
+         ui.set_max_height(400.0);
          ui.radio_value(&mut self.b, false, "False");
          ui.radio_value(&mut self.b, true, "True");

          if self.b {
              egui::ScrollArea::vertical().show(ui, |ui| {
+                 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
                  for _ in row_count {
                      ui.add_space(30.0);
                      ui.label("Veeeeeeeeeeeery long text.");
                  }
              });
          }
+     });
  })

@lucasmerlin
Copy link
Collaborator

lucasmerlin commented Sep 20, 2024

I'm having the same problem in my app, my workaround is to call this at the beginning of the Area:

    let screen_size = ui.ctx().screen_rect().size();
    ui.set_max_size(screen_size);

But I think this is pretty ugly, maybe we can add a Area::remember_size(bool) or Area::limit_size(bool) option?
I think this changed with the 0.28 release, maybe in #4557?

@emilk emilk added this to the Next Major Release milestone Sep 20, 2024
@emilk
Copy link
Owner

emilk commented Sep 22, 2024

There are two problems here, one that is trivial, the other which is not.

Trivial problem: Why is the ScrollArea so small?

Let's look at this simpler case:

use eframe::egui;

fn main() -> eframe::Result {
    eframe::run_simple_native("My egui App", Default::default(), move |ctx, _frame| {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.menu_button("Menu", |ui| {
                if ui.button("Close menu").clicked() {
                    ui.close_menu();
                }
                ui.collapsing("Collapsing", |ui|{
                    egui::ScrollArea::both().show(ui, |ui|{
                        for _ in 0..10 {
                            ui.label("This is a long text label containing a lot of words and spans many lines");
                        }
                    });
                });
            });
        });
    })
}

Results in this:

image

Now why is the ScrollArea so small? Well, why wouldn't it be? There is nothing in the code preventing it from being small.
Let's fix that:

-                    egui::ScrollArea::both().show(ui, |ui|{
+                    egui::ScrollArea::both()
+                        .min_scrolled_width(300.0)
+                        .min_scrolled_height(200.0)
+                        .show(ui, |ui|
+                    {

image

Great, now the scroll area has some size! But what happens when we collapse it?

image

The menu stays wide! This is the second problem.

Real problem: How to auto-size menus with dynamic content

A menu defaults to a justified layout, which means buttons span the full width. This is how drop-down menus in most apps work. But that also means the parent ui can't know it needs to shrink, because all the buttons use up all the available width.

The path to fixing this is non-obvious.

Report actual minimum sizes

We could have a system where all widgets and Uis report their actual required minimum size somehow.
This only works in a few narrow cases (see the new intrinsic_size from #5082).
But in the general case that would require a big redesign of the egui layout code, and I doubt it would be possible even in that case. But maybe?

Leverage the existing sizing_pass

There is a UiBuilder::sizing_pass flag that tells all layouts and widgets "use as little space as possible". All menus (in fact, all Areas) set this flag the first frame they are shown.

During a sizing pass widgets are laid out a bit weird, so we should cover it up by calling the new Context::request_discard.

We somehow need to trigger this re-measuirement when the content changes (e.g. the collapsing header in the example changes).

How?

Manual call?

We could have something like ui.discard_measurements() that would trickle up the call-stack and cause the Area to forget its current size and do a new sizing pass the next frame. CollapsingHeader could call that for you, for instance.
I'm not quite sure how to implement it, but it definitely should be doable.

Automatic size detection?

We could consider having the Area detect when the content changes, and trigger a new sizing pass in these cases.

For instance, it could be triggered if the area ends up with a different size that it had the previous frame.
In this case, that would trigger a sizing_pass on several consecutive frames while the CollappsingHeader animates. Maybe this is fine, though it would also mean several frames in a row with request_discard, which would mean a performance hit.

This could be fixed with a logic along the lines of "if the content changes, mark Area as need of a new sizing pass, but don't run the sizing pass until the size is stable". This would mean the collapsing header would first collapse, and then once closed, the menu would become narrow again. Which I guess is exactly what we want?

@emilk emilk changed the title Improve popup menu resizing with dynamic content Improve Area sizing with dynamic content Sep 25, 2024
emilk added a commit that referenced this issue Sep 25, 2024
Affects `.on_hover_text(…)` with dynamic content (i.e. content that
changes over time).

* Closes #5167

`.on_hover_ui` with dynamic content can still hit the shrinking problem.
The general solution depends on solving
#5138 but a work-around is to add
this to your tooltips:

```diff
 response.on_hover_ui(|ui| {
+    ui.set_max_width(ui.spacing().tooltip_width);
     // …
 });
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is broken egui
Projects
None yet
Development

No branches or pull requests

3 participants