mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Fix window with a Grid being widenable but not shrinkable again (#8386)
## Related * Fixes a regression from #8152 * Part of #2921 Reported symptom: you can widen the Widget Gallery window, but it won't shrink again. I'm not sure this fix is the best one, but it does work. # Claude says ## Cause A `Grid` gives its **last** column all the available width, so a width-filling widget in it (`Separator`, `TextEdit`, `ProgressBar`, …) makes the `Grid` remember a `col_width` that is really just "however wide we happened to be". At the start of a resize drag, `Resize` runs a one-frame sizing pass (#8152) to measure the minimum content width and clamps the drag against it. But `GridLayout::next_cell` inflated every cell to `prev_state.col_width`, so the `Grid` reported its previous width as its minimum — even though it was only offered `min_size.x`. The clamp is a lower bound, so widening kept working while shrinking was blocked at the widened width. ## Fix During an enclosing sizing pass, don't inflate the stretchy last column to its remembered width, and don't store the measured (narrow) widths. Minimal repro (fails before, passes after — added as a regression test): ```rust Window::new("x").default_width(280.0).show(ctx, |ui| { egui::Grid::new("grid").num_columns(2).show(ui, |ui| { ui.label("Separator"); ui.separator(); // fills the last column ui.end_row(); }); }); ``` `Panel` is unaffected — it clamps only against the user's `min_size`, with no content-min sizing pass. * [x] I have followed the instructions in the PR template 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
This commit is contained in:
@@ -75,6 +75,11 @@ pub(crate) struct GridLayout {
|
|||||||
curr_state: State,
|
curr_state: State,
|
||||||
initial_available: Rect,
|
initial_available: Rect,
|
||||||
|
|
||||||
|
/// Are we inside an enclosing sizing pass (e.g. [`crate::Resize`] measuring
|
||||||
|
/// the minimum content width)? If so we must not remember the (narrow) sizes
|
||||||
|
/// we measure during it.
|
||||||
|
sizing_pass: bool,
|
||||||
|
|
||||||
// Options:
|
// Options:
|
||||||
num_columns: Option<usize>,
|
num_columns: Option<usize>,
|
||||||
spacing: Vec2,
|
spacing: Vec2,
|
||||||
@@ -90,6 +95,10 @@ pub(crate) struct GridLayout {
|
|||||||
impl GridLayout {
|
impl GridLayout {
|
||||||
pub(crate) fn new(ui: &Ui, id: Id, prev_state: Option<State>) -> Self {
|
pub(crate) fn new(ui: &Ui, id: Id, prev_state: Option<State>) -> Self {
|
||||||
let is_first_frame = prev_state.is_none();
|
let is_first_frame = prev_state.is_none();
|
||||||
|
|
||||||
|
// An outer sizing pass, we should render as small as possible.
|
||||||
|
let sizing_pass = ui.is_sizing_pass();
|
||||||
|
|
||||||
let prev_state = prev_state.unwrap_or_default();
|
let prev_state = prev_state.unwrap_or_default();
|
||||||
|
|
||||||
// TODO(emilk): respect current layout
|
// TODO(emilk): respect current layout
|
||||||
@@ -110,6 +119,7 @@ impl GridLayout {
|
|||||||
prev_state,
|
prev_state,
|
||||||
curr_state: State::default(),
|
curr_state: State::default(),
|
||||||
initial_available,
|
initial_available,
|
||||||
|
sizing_pass,
|
||||||
|
|
||||||
num_columns: None,
|
num_columns: None,
|
||||||
spacing: ui.spacing().item_spacing,
|
spacing: ui.spacing().item_spacing,
|
||||||
@@ -180,7 +190,11 @@ impl GridLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_cell(&self, cursor: Rect, child_size: Vec2) -> Rect {
|
pub(crate) fn next_cell(&self, cursor: Rect, child_size: Vec2) -> Rect {
|
||||||
let width = self.prev_state.col_width(self.col).unwrap_or(0.0);
|
let width = if self.sizing_pass {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
self.prev_state.col_width(self.col).unwrap_or(0.0)
|
||||||
|
};
|
||||||
let height = self.prev_row_height(self.row);
|
let height = self.prev_row_height(self.row);
|
||||||
let size = child_size.max(vec2(width, height));
|
let size = child_size.max(vec2(width, height));
|
||||||
Rect::from_min_size(cursor.min, size).round_ui()
|
Rect::from_min_size(cursor.min, size).round_ui()
|
||||||
|
|||||||
@@ -513,6 +513,61 @@ fn window_resize_wraps_to_content_min_width() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `Grid` gives its last column all the available width, so a width-filling widget in it
|
||||||
|
/// (here a `Separator`) makes the grid remember a column width that is really just
|
||||||
|
/// "however wide the window happened to be".
|
||||||
|
///
|
||||||
|
/// When `Resize` then measures the minimum content width in a sizing pass, that remembered
|
||||||
|
/// width must not be reported as the minimum — otherwise the window can be widened but
|
||||||
|
/// never shrunk again.
|
||||||
|
#[test]
|
||||||
|
fn window_with_grid_can_shrink_after_being_widened() {
|
||||||
|
let window_title = "grid_shrink_regression";
|
||||||
|
let mut harness = Harness::builder()
|
||||||
|
.with_size(Vec2::new(800.0, 600.0))
|
||||||
|
.build_ui(move |ui| {
|
||||||
|
Window::new(window_title)
|
||||||
|
.default_pos([20.0, 20.0])
|
||||||
|
.default_width(280.0)
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
egui::Grid::new("grid").num_columns(2).show(ui, |ui| {
|
||||||
|
ui.label("Separator");
|
||||||
|
ui.separator(); // Fills the available width
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
let drag_right_edge = |harness: &mut Harness<'_>, dx: f32| {
|
||||||
|
let rect = harness
|
||||||
|
.get_by_role_and_label(Role::Window, window_title)
|
||||||
|
.rect();
|
||||||
|
let grab = Pos2::new(rect.right(), rect.center().y);
|
||||||
|
harness.hover_at(grab);
|
||||||
|
harness.run();
|
||||||
|
harness.drag_at(grab);
|
||||||
|
harness.run();
|
||||||
|
harness.hover_at(grab + Vec2::new(dx, 0.0));
|
||||||
|
harness.run();
|
||||||
|
harness.drop_at(grab + Vec2::new(dx, 0.0));
|
||||||
|
harness.run();
|
||||||
|
harness
|
||||||
|
.get_by_role_and_label(Role::Window, window_title)
|
||||||
|
.rect()
|
||||||
|
.width()
|
||||||
|
};
|
||||||
|
|
||||||
|
let widened = drag_right_edge(&mut harness, 300.0);
|
||||||
|
let shrunk = drag_right_edge(&mut harness, -300.0);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
shrunk < widened - 200.0,
|
||||||
|
"window could not be shrunk again after being widened: \
|
||||||
|
widened to {widened}, then only shrunk to {shrunk}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure that the size passed to window is actually treated as outer size (including
|
/// Ensure that the size passed to window is actually treated as outer size (including
|
||||||
/// margins and borders).
|
/// margins and borders).
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user