1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -04:00

Experiment with calculating an Ui's "intrinsic size" by remembering the widget's size

This commit is contained in:
Lucas Meurer
2025-03-02 11:48:37 +01:00
parent 962c7c7516
commit 80d218cf71
8 changed files with 87 additions and 16 deletions

View File

@@ -613,7 +613,9 @@ impl Prepared {
..
} = self;
state.size = Some(content_ui.min_size());
state.size = Some(content_ui.placer().min_item_size());
dbg!(state.size);
// Make sure we report back the correct size.
// Very important after the initial sizing pass, when the initial estimate of the size is way off.

View File

@@ -568,7 +568,8 @@ impl<'a> Popup<'a> {
let mut response = area.show(&ctx, |ui| {
style.apply(ui.style_mut());
frame.show(ui, content).inner
// frame.show(ui, content).inner
content(ui)
});
let closed_by_click = match close_behavior {

View File

@@ -45,6 +45,8 @@ pub(crate) struct Region {
/// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child.
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`.
pub(crate) cursor: Rect,
pub min_item_size: Vec2,
}
impl Region {
@@ -426,6 +428,7 @@ impl Layout {
min_rect: Rect::NOTHING, // temporary
max_rect,
cursor: self.initial_cursor(max_rect),
min_item_size: Vec2::default(),
};
let seed = self.next_widget_position(&region);
region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
@@ -529,6 +532,7 @@ impl Layout {
mut cursor,
mut max_rect,
min_rect,
min_item_size,
} = *region;
match self.main_dir {
@@ -590,6 +594,7 @@ impl Layout {
min_rect,
max_rect,
cursor,
min_item_size,
};
self.next_frame_ignore_wrap(&region, child_size)

View File

@@ -163,6 +163,7 @@ impl Placer {
frame_rect: Rect,
widget_rect: Rect,
item_spacing: Vec2,
desired_size: Vec2,
) {
debug_assert!(!frame_rect.any_nan());
debug_assert!(!widget_rect.any_nan());
@@ -179,11 +180,17 @@ impl Placer {
);
}
self.region.min_item_size = self.region.min_item_size.max(desired_size);
self.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
self.region.sanity_check();
}
pub(crate) fn min_item_size(&self) -> Vec2 {
self.region.min_item_size
}
/// Move to the next row in a grid layout or wrapping layout.
/// Otherwise does nothing.
pub(crate) fn end_row(&mut self, item_spacing: Vec2, painter: &Painter) {

View File

@@ -1403,7 +1403,7 @@ impl Ui {
let widget_rect = self.placer.justify_and_align(frame_rect, desired_size);
self.placer
.advance_after_rects(frame_rect, widget_rect, item_spacing);
.advance_after_rects(frame_rect, widget_rect, item_spacing, desired_size);
register_rect(self, widget_rect);
@@ -1426,7 +1426,8 @@ impl Ui {
let rect = rect.round_ui();
let item_spacing = self.spacing().item_spacing;
self.placer.advance_after_rects(rect, rect, item_spacing);
self.placer
.advance_after_rects(rect, rect, item_spacing, rect.size());
register_rect(self, rect);
let id = Id::new(self.next_auto_id_salt);
@@ -2412,8 +2413,22 @@ impl Ui {
let mut child_ui = self.new_child(ui_builder);
self.next_auto_id_salt = next_auto_id_salt; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
let ret = add_contents(&mut child_ui);
let response = child_ui.remember_min_rect();
let mut response = child_ui.remember_min_rect();
self.advance_cursor_after_rect(child_ui.min_rect());
match self.layout().is_horizontal() {
true => {
response.intrinsic_size = Some(Vec2::new(
response.rect.width(),
self.placer.min_item_size().y,
));
}
false => {
response.intrinsic_size = Some(Vec2::new(
self.placer.min_item_size().x,
response.rect.height(),
));
}
}
InnerResponse::new(ret, response)
}

View File

@@ -75,6 +75,7 @@ impl Widget for Checkbox<'_> {
desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y));
desired_size.y = desired_size.y.max(icon_width);
dbg!(desired_size);
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
if response.clicked() {

View File

@@ -70,6 +70,18 @@ fn nested_menus(ui: &mut egui::Ui, checked: &mut bool) {
}
let _ = ui.button("Item");
ui.menu_button("Recursive", |ui| nested_menus(ui, checked));
// if ui.button(if *checked { "short" } else { "Very long text for this item that should be wrapped" }).clicked() {
// *checked = !*checked;
// }
ui.checkbox(
checked,
if *checked {
"short"
} else {
"Very long text for this item that should be wrapped"
},
);
});
ui.menu_button("SubMenu", |ui| {
if ui.button("Open…").clicked() {

View File

@@ -2,6 +2,7 @@
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::egui;
use eframe::egui::{Align, Button, Layout, Popup, TextWrapMode, Widget};
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
@@ -15,19 +16,46 @@ fn main() -> eframe::Result {
let mut name = "Arthur".to_owned();
let mut age = 42;
let mut checked = true;
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
if ui.button("Increment").clicked() {
age += 1;
}
ui.label(format!("Hello '{name}', age {age}"));
// ui.heading("My egui Application");
// ui.horizontal(|ui| {
// let name_label = ui.label("Your name: ");
// ui.text_edit_singleline(&mut name)
// .labelled_by(name_label.id);
// });
// ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
// if ui.button("Increment").clicked() {
// age += 1;
// }
// ui.label(format!("Hello '{name}', age {age}"));
let response = ui.button("Hiiii");
let text = if checked {
"short"
} else {
"Very long text for this item that should be wrapped"
};
Popup::from_response(&response)
.layout(Layout::top_down_justified(Align::Min))
.show(|ui| {
// ui.checkbox(&mut checked, text);
ui.button("Hiiii");
if Button::new(text)
.wrap_mode(TextWrapMode::Extend)
.ui(ui)
.clicked()
{
checked = !checked;
}
ui.button("Some other button");
});
});
})
}