diff --git a/egui/src/grid.rs b/egui/src/grid.rs index ddd3ca01..060171d8 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -61,6 +61,15 @@ impl GridLayout { pub(crate) fn new(ui: &Ui, id: Id) -> Self { let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default(); + // TODO: respect current layout + + let available = ui.placer().max_rect().intersect(ui.cursor()); + let initial_x = available.min.x; + assert!( + initial_x.is_finite(), + "Grid not yet available for right-to-left layouts" + ); + Self { ctx: ui.ctx().clone(), style: ui.style().clone(), @@ -69,7 +78,7 @@ impl GridLayout { curr_state: State::default(), spacing: ui.spacing().item_spacing, striped: false, - initial_x: ui.cursor().x, + initial_x, min_cell_size: ui.spacing().interact_size, max_cell_size: Vec2::INFINITY, col: 0, @@ -95,10 +104,6 @@ impl GridLayout { } pub(crate) fn available_rect(&self, region: &Region) -> Rect { - // let mut rect = Rect::from_min_max(region.cursor, region.max_rect.max); - // rect.set_height(rect.height().at_least(self.min_cell_size.y)); - // rect - // required for putting CollapsingHeader in anything but the last column: self.available_rect_finite(region) } @@ -116,19 +121,21 @@ impl GridLayout { .unwrap_or(self.min_cell_size.x) }; - let height = region.max_rect_finite().max.y - region.cursor.y; + let available = region.max_rect.intersect(region.cursor); + + let height = region.max_rect_finite().max.y - available.top(); let height = height .at_least(self.min_cell_size.y) .at_most(self.max_cell_size.y); - Rect::from_min_size(region.cursor, vec2(width, height)) + Rect::from_min_size(available.min, vec2(width, height)) } - pub(crate) fn next_cell(&self, cursor: Pos2, 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 height = self.prev_row_height(self.row); let size = child_size.max(vec2(width, height)); - Rect::from_min_size(cursor, size) + Rect::from_min_size(cursor.min, size) } pub(crate) fn align_size_within_rect(&self, size: Vec2, frame: Rect) -> Rect { @@ -140,7 +147,7 @@ impl GridLayout { self.align_size_within_rect(size, frame) } - pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) { + pub(crate) fn advance(&mut self, cursor: &mut Rect, frame_rect: Rect, widget_rect: Rect) { let debug_expand_width = self.style.visuals.debug_expand_width; let debug_expand_height = self.style.visuals.debug_expand_height; if debug_expand_width || debug_expand_height { @@ -171,14 +178,14 @@ impl GridLayout { ); self.col += 1; - cursor.x += frame_rect.width() + self.spacing.x; + cursor.min.x += frame_rect.width() + self.spacing.x; } - pub(crate) fn end_row(&mut self, cursor: &mut Pos2, painter: &Painter) { + pub(crate) fn end_row(&mut self, cursor: &mut Rect, painter: &Painter) { let row_height = self.prev_row_height(self.row); - cursor.x = self.initial_x; - cursor.y += row_height + self.spacing.y; + cursor.min.x = self.initial_x; + cursor.min.y += row_height + self.spacing.y; self.col = 0; self.row += 1; @@ -186,7 +193,7 @@ impl GridLayout { if let Some(height) = self.prev_state.row_height(self.row) { // Paint background for coming row: let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height); - let rect = Rect::from_min_size(*cursor, size); + let rect = Rect::from_min_size(cursor.min, size); let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y); let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides diff --git a/egui/src/layout.rs b/egui/src/layout.rs index f0620770..6dbff720 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -1,4 +1,5 @@ use crate::{emath::*, Align}; +use std::f32::INFINITY; // ---------------------------------------------------------------------------- @@ -29,9 +30,16 @@ pub(crate) struct Region { pub max_rect: Rect, /// Where the next widget will be put. + /// + /// One side of this will always be infinite: the direction in which new widgets will be added. + /// The opposing side is what is incremented. + /// The crossing sides are initialized to `max_rect`. + /// + /// So one can think of `cursor` as a constraint on the available region. + /// /// If something has already been added, this will point ot `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: Pos2, + pub(crate) cursor: Rect, } impl Region { @@ -61,14 +69,18 @@ impl Region { self.max_rect = self.max_rect.union(rect); } - /// Ensure we are big enough to contain the given x-coordinate. + /// Ensure we are big enough to contain the given X-coordinate. /// This is sometimes useful to expand an ui to stretch to a certain place. pub fn expand_to_include_x(&mut self, x: f32) { - self.min_rect.min.x = self.min_rect.min.x.min(x); - self.min_rect.max.x = self.min_rect.max.x.max(x); + self.min_rect.extend_with_x(x); + self.max_rect.extend_with_x(x); + } - self.max_rect.min.x = self.max_rect.min.x.min(x); - self.max_rect.max.x = self.max_rect.max.x.max(x); + /// Ensure we are big enough to contain the given Y-coordinate. + /// This is sometimes useful to expand an ui to stretch to a certain place. + pub fn expand_to_include_y(&mut self, y: f32) { + self.min_rect.extend_with_y(y); + self.max_rect.extend_with_y(y); } } @@ -323,49 +335,55 @@ impl Layout { self.align2().align_size_within_rect(size, outer) } - fn initial_cursor(&self, max_rect: Rect) -> Pos2 { + fn initial_cursor(&self, max_rect: Rect) -> Rect { + let mut cursor = max_rect; + match self.main_dir { - Direction::LeftToRight => max_rect.left_top(), - Direction::RightToLeft => max_rect.right_top(), - Direction::TopDown => max_rect.left_top(), - Direction::BottomUp => max_rect.left_bottom(), + Direction::LeftToRight => { + cursor.max.x = INFINITY; + } + Direction::RightToLeft => { + cursor.min.x = -INFINITY; + } + Direction::TopDown => { + cursor.max.y = INFINITY; + } + Direction::BottomUp => { + cursor.min.y = -INFINITY; + } } + + cursor } pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region { - let cursor = self.initial_cursor(max_rect); - let min_rect = Rect::from_min_size(cursor, Vec2::ZERO); - Region { - min_rect, + debug_assert!(!max_rect.any_nan()); + let mut region = Region { + min_rect: Rect::NOTHING, // temporary max_rect, - cursor, - } + cursor: self.initial_cursor(max_rect), + }; + let seed = self.next_widget_position(®ion); + region.min_rect = Rect::from_center_size(seed, Vec2::ZERO); + region } pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect { self.available_from_cursor_max_rect(region.cursor, region.max_rect) } - fn available_size_before_wrap(&self, region: &Region) -> Vec2 { - self.available_rect_before_wrap(region).size() - } - pub(crate) fn available_rect_before_wrap_finite(&self, region: &Region) -> Rect { self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite()) } - fn available_size_before_wrap_finite(&self, region: &Region) -> Vec2 { - self.available_rect_before_wrap_finite(region).size() - } - /// Amount of space available for a widget. /// For wrapping layouts, this is the maximum (after wrap). pub(crate) fn available_size(&self, r: &Region) -> Vec2 { if self.main_wrap { if self.main_dir.is_horizontal() { - vec2(r.max_rect.width(), r.max_rect.bottom() - r.cursor.y) + vec2(r.max_rect.width(), r.cursor.height()) } else { - vec2(r.max_rect.right() - r.cursor.x, r.max_rect.height()) + vec2(r.cursor.width(), r.max_rect.height()) } } else { self.available_from_cursor_max_rect(r.cursor, r.max_rect) @@ -375,123 +393,159 @@ impl Layout { /// Given the cursor in the region, how much space is available /// for the next widget? - fn available_from_cursor_max_rect(&self, cursor: Pos2, max_rect: Rect) -> Rect { - let mut rect = max_rect; - - match self.main_dir { - Direction::LeftToRight => { - rect.min.x = cursor.x; - rect.min.y = cursor.y; - } - Direction::RightToLeft => { - rect.max.x = cursor.x; - rect.min.y = cursor.y; - } - Direction::TopDown => { - rect.min.x = cursor.x; - rect.min.y = cursor.y; - } - Direction::BottomUp => { - rect.min.x = cursor.x; - rect.max.y = cursor.y; - } - } - - rect + fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect { + max_rect.intersect(cursor) } /// Returns where to put the next widget that is of the given size. /// The returned `frame_rect` `Rect` will always be justified along the cross axis. /// This is what you then pass to `advance_after_rects`. /// Use `justify_and_align` to get the inner `widget_rect`. - #[allow(clippy::collapsible_if)] - pub(crate) fn next_space( - &self, - region: &Region, - mut child_size: Vec2, - item_spacing: Vec2, - ) -> Rect { - let mut cursor = region.cursor; - + pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect { if self.main_wrap { - let available_size = self.available_size_before_wrap(region); + let available_size = self.available_rect_before_wrap(region).size(); + + let Region { + mut cursor, + mut max_rect, + min_rect, + } = *region; + match self.main_dir { Direction::LeftToRight => { - if available_size.x < child_size.x && region.max_rect.left() < cursor.x { + if available_size.x < child_size.x && max_rect.left() < cursor.left() { // New row - cursor = pos2( - region.max_rect.left(), - region.max_rect.bottom() + item_spacing.y, + let new_row_height = cursor.height().max(child_size.y); + cursor = Rect::from_min_max( + pos2(max_rect.left(), cursor.bottom() + spacing.y), + pos2(INFINITY, cursor.bottom() + spacing.y + new_row_height), ); + max_rect.max.y = max_rect.max.y.max(cursor.max.y); } } Direction::RightToLeft => { - if available_size.x < child_size.x && cursor.x < region.max_rect.right() { + if available_size.x < child_size.x && cursor.right() < max_rect.right() { // New row - cursor = pos2( - region.max_rect.right(), - region.max_rect.bottom() + item_spacing.y, + let new_row_height = cursor.height().max(child_size.y); + cursor = Rect::from_min_max( + pos2(-INFINITY, cursor.bottom() + spacing.y), + pos2( + max_rect.right(), + cursor.bottom() + spacing.y + new_row_height, + ), ); + max_rect.max.y = max_rect.max.y.max(cursor.max.y); } } Direction::TopDown => { - if available_size.y < child_size.y && region.max_rect.top() < cursor.y { + if available_size.y < child_size.y && max_rect.top() < cursor.top() { // New column - cursor = pos2( - region.max_rect.right() + item_spacing.x, - region.max_rect.top(), + let new_col_width = cursor.width().max(child_size.x); + cursor = Rect::from_min_max( + pos2(cursor.right() + spacing.x, max_rect.top()), + pos2(cursor.right() + spacing.x + new_col_width, INFINITY), ); + max_rect.max.x = max_rect.max.x.max(cursor.max.x); } } Direction::BottomUp => { - if available_size.y < child_size.y && cursor.y < region.max_rect.bottom() { + if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() { // New column - cursor = pos2( - region.max_rect.right() + item_spacing.x, - region.max_rect.bottom(), + let new_col_width = cursor.width().max(child_size.x); + cursor = Rect::from_min_max( + pos2(cursor.right() + spacing.x, -INFINITY), + pos2( + cursor.right() + spacing.x + new_col_width, + max_rect.bottom(), + ), ); + max_rect.max.x = max_rect.max.x.max(cursor.max.x); } } } - } - let available_size = self.available_size_before_wrap_finite(region); + // Use the new cursor: + let region = Region { + min_rect, + max_rect, + cursor, + }; + + self.next_frame_ignore_wrap(®ion, child_size) + } else { + self.next_frame_ignore_wrap(region, child_size) + } + } + + fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect { + let available_rect = self.available_rect_before_wrap_finite(region); + + let mut frame_size = child_size; if self.is_vertical() || self.horizontal_justify() { - child_size.x = child_size.x.at_least(available_size.x); // fill full width + frame_size.x = frame_size.x.at_least(available_rect.width()); // fill full width } if self.is_horizontal() || self.vertical_justify() { - child_size.y = child_size.y.at_least(available_size.y); // fill full height + frame_size.y = frame_size.y.at_least(available_rect.height()); // fill full height } - let child_pos = match self.main_dir { - Direction::LeftToRight => cursor, - Direction::RightToLeft => cursor + vec2(-child_size.x, 0.0), - Direction::TopDown => cursor, - Direction::BottomUp => cursor + vec2(0.0, -child_size.y), + let align2 = match self.main_dir { + Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]), + Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]), + Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]), + Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]), }; - Rect::from_min_size(child_pos, child_size) + align2.align_size_within_rect(frame_size, available_rect) } /// Apply justify (fill width/height) and/or alignment after calling `next_space`. - pub(crate) fn justify_and_align(&self, rect: Rect, mut child_size: Vec2) -> Rect { + pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect { if self.horizontal_justify() { - child_size.x = child_size.x.at_least(rect.width()); // fill full width + child_size.x = child_size.x.at_least(frame.width()); // fill full width } if self.vertical_justify() { - child_size.y = child_size.y.at_least(rect.height()); // fill full height + child_size.y = child_size.y.at_least(frame.height()); // fill full height } - self.align_size_within_rect(child_size, rect) + self.align_size_within_rect(child_size, frame) } - /// Advance the cursor by this many points. - pub(crate) fn advance_cursor(&self, cursor: &mut Pos2, amount: f32) { + pub(crate) fn next_widget_space_ignore_wrap_justify( + &self, + region: &Region, + size: Vec2, + ) -> Rect { + let frame = self.next_frame_ignore_wrap(region, size); + let rect = self.align_size_within_rect(size, frame); + debug_assert!((rect.size() - size).length() < 1.0); + rect + } + + /// Where would the next tiny widget be centered? + pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 { + self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO) + .center() + } + + /// Advance the cursor by this many points, and allocate in region. + pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) { match self.main_dir { - Direction::LeftToRight => cursor.x += amount, - Direction::RightToLeft => cursor.x -= amount, - Direction::TopDown => cursor.y += amount, - Direction::BottomUp => cursor.y -= amount, + Direction::LeftToRight => { + region.cursor.min.x += amount; + region.expand_to_include_x(region.cursor.min.x); + } + Direction::RightToLeft => { + region.cursor.max.x -= amount; + region.expand_to_include_x(region.cursor.max.x); + } + Direction::TopDown => { + region.cursor.min.y += amount; + region.expand_to_include_y(region.cursor.min.y); + } + Direction::BottomUp => { + region.cursor.max.y -= amount; + region.expand_to_include_y(region.cursor.max.y); + } } } @@ -501,28 +555,83 @@ impl Layout { /// * `widget_rect`: the actual rect used by the widget pub(crate) fn advance_after_rects( &self, - cursor: &mut Pos2, + cursor: &mut Rect, frame_rect: Rect, widget_rect: Rect, item_spacing: Vec2, ) { - *cursor = match self.main_dir { - Direction::LeftToRight => pos2(widget_rect.right() + item_spacing.x, frame_rect.top()), - Direction::RightToLeft => pos2(widget_rect.left() - item_spacing.x, frame_rect.top()), - Direction::TopDown => pos2(frame_rect.left(), widget_rect.bottom() + item_spacing.y), - Direction::BottomUp => pos2(frame_rect.left(), widget_rect.top() - item_spacing.y), + if self.main_wrap { + if cursor.intersects(frame_rect) { + // make row/column larger if necessary + *cursor = cursor.union(frame_rect); + } else { + // this is a new row or column. We temporarily use NAN for what will be filled in later. + match self.main_dir { + Direction::LeftToRight => { + *cursor = Rect::from_min_max( + pos2(f32::NAN, frame_rect.min.y), + pos2(INFINITY, frame_rect.max.y), + ); + } + Direction::RightToLeft => { + *cursor = Rect::from_min_max( + pos2(-INFINITY, frame_rect.min.y), + pos2(f32::NAN, frame_rect.max.y), + ); + } + Direction::TopDown => { + *cursor = Rect::from_min_max( + pos2(frame_rect.min.x, f32::NAN), + pos2(frame_rect.max.x, INFINITY), + ); + } + Direction::BottomUp => { + *cursor = Rect::from_min_max( + pos2(frame_rect.min.x, -INFINITY), + pos2(frame_rect.max.x, f32::NAN), + ); + } + }; + } + } + + match self.main_dir { + Direction::LeftToRight => { + cursor.min.x = widget_rect.right() + item_spacing.x; + } + Direction::RightToLeft => { + cursor.max.x = widget_rect.left() - item_spacing.x; + } + Direction::TopDown => { + cursor.min.y = widget_rect.bottom() + item_spacing.y; + } + Direction::BottomUp => { + cursor.max.y = widget_rect.top() - item_spacing.y; + } }; } /// Move to the next row in a wrapping layout. /// Otherwise does nothing. - pub(crate) fn end_row(&mut self, region: &mut Region, item_spacing: Vec2) { - if self.main_wrap && self.is_horizontal() { - // New row - region.cursor = pos2( - region.max_rect.left(), - region.max_rect.bottom() + item_spacing.y, - ); + pub(crate) fn end_row(&mut self, region: &mut Region, spacing: Vec2) { + if self.main_wrap { + match self.main_dir { + Direction::LeftToRight => { + let new_top = region.cursor.bottom() + spacing.y; + region.cursor = Rect::from_min_max( + pos2(region.max_rect.left(), new_top), + pos2(INFINITY, new_top + region.cursor.height()), + ); + } + Direction::RightToLeft => { + let new_top = region.cursor.bottom() + spacing.y; + region.cursor = Rect::from_min_max( + pos2(-INFINITY, new_top), + pos2(region.max_rect.right(), new_top + region.cursor.height()), + ); + } + Direction::TopDown | Direction::BottomUp => {} + } } } } @@ -540,7 +649,7 @@ impl Layout { ) { use epaint::*; - let cursor = region.cursor; + let cursor = self.next_widget_position(region); let align; diff --git a/egui/src/placer.rs b/egui/src/placer.rs index 9df78265..babba0be 100644 --- a/egui/src/placer.rs +++ b/egui/src/placer.rs @@ -59,7 +59,7 @@ impl Placer { self.region.min_rect = min_rect; } - pub(crate) fn cursor(&self) -> Pos2 { + pub(crate) fn cursor(&self) -> Rect { self.region.cursor } } @@ -108,12 +108,24 @@ impl Placer { grid.next_cell(self.region.cursor, child_size) } else { self.layout - .next_space(&self.region, child_size, item_spacing) + .next_frame(&self.region, child_size, item_spacing) + } + } + + /// Where do we expect a zero-sized widget to be placed? + pub(crate) fn next_widget_position(&self) -> Pos2 { + if let Some(grid) = &self.grid { + grid.next_cell(self.region.cursor, Vec2::ZERO).center() + } else { + self.layout.next_widget_position(&self.region) } } /// Apply justify or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect { + debug_assert!(!rect.any_nan()); + debug_assert!(!child_size.any_nan()); + if let Some(grid) = &self.grid { grid.justify_and_align(rect, child_size) } else { @@ -128,10 +140,7 @@ impl Placer { self.grid.is_none(), "You cannot advance the cursor when in a grid layout" ); - self.layout.advance_cursor(&mut self.region.cursor, amount); - - self.region - .expand_to_include_rect(Rect::from_min_size(self.cursor(), Vec2::ZERO)); + self.layout.advance_cursor(&mut self.region, amount); } /// Advance cursor after a widget was added to a specific rectangle @@ -155,7 +164,7 @@ impl Placer { item_spacing, ) } - self.region.expand_to_include_rect(widget_rect); + self.region.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame } /// Move to the next row in a grid layout or wrapping layout. @@ -180,68 +189,45 @@ impl Placer { self.region.expand_to_include_x(x); } + fn next_widget_space_ignore_wrap_justify(&self, size: Vec2) -> Rect { + self.layout + .next_widget_space_ignore_wrap_justify(&self.region, size) + } + /// Set the maximum width of the ui. /// You won't be able to shrink it below the current minimum size. pub(crate) fn set_max_width(&mut self, width: f32) { - #![allow(clippy::float_cmp)] - let Self { layout, region, .. } = self; - if layout.main_dir() == Direction::RightToLeft { - debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x); - region.max_rect.min.x = region.max_rect.max.x - width.at_least(region.min_rect.width()); - } else { - debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x); - region.max_rect.max.x = region.max_rect.min.x + width.at_least(region.min_rect.width()); - } + let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0)); + let region = &mut self.region; + region.max_rect.min.x = rect.min.x; + region.max_rect.max.x = rect.max.x; + region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much } /// Set the maximum height of the ui. /// You won't be able to shrink it below the current minimum size. pub(crate) fn set_max_height(&mut self, height: f32) { - #![allow(clippy::float_cmp)] - let Self { layout, region, .. } = self; - if layout.main_dir() == Direction::BottomUp { - debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y); - region.max_rect.min.y = - region.max_rect.max.y - height.at_least(region.min_rect.height()); - } else { - debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y); - region.max_rect.max.y = - region.max_rect.min.y + height.at_least(region.min_rect.height()); - } + let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height)); + let region = &mut self.region; + region.max_rect.min.y = rect.min.y; + region.max_rect.max.y = rect.max.y; + region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much } /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_width(&mut self, width: f32) { - #![allow(clippy::float_cmp)] - let Self { layout, region, .. } = self; - if layout.main_dir() == Direction::RightToLeft { - debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x); - let min_rect = &mut region.min_rect; - min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width); - } else { - debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x); - let min_rect = &mut region.min_rect; - min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width); - } - region.max_rect = region.max_rect.union(region.min_rect); + let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0)); + self.region.expand_to_include_x(rect.min.x); + self.region.expand_to_include_x(rect.max.x); } /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_height(&mut self, height: f32) { - #![allow(clippy::float_cmp)] - let Self { layout, region, .. } = self; - if layout.main_dir() == Direction::BottomUp { - debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y); - let min_rect = &mut region.min_rect; - min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height); - } else { - debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y); - let min_rect = &mut region.min_rect; - min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height); - } - region.max_rect = region.max_rect.union(region.min_rect); + let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height)); + self.region.expand_to_include_y(rect.min.y); + self.region.expand_to_include_y(rect.max.y); } } diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 33c20f0a..548be78e 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -73,6 +73,7 @@ impl Ui { } pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self { + debug_assert!(!max_rect.any_nan()); self.next_auto_id = self.next_auto_id.wrapping_add(1); Ui { @@ -645,7 +646,9 @@ impl Ui { } /// Allocate a specific part of the `Ui‘. + /// /// Ignore the layout of the `Ui‘: just put my widget here! + /// The layout cursor will advance to past this `rect`. pub(crate) fn allocate_rect(&mut self, rect: Rect, sense: Sense) -> Response { let id = self.advance_cursor_after_rect(rect); self.interact(rect, id, sense) @@ -659,10 +662,19 @@ impl Ui { Id::new(self.next_auto_id) } - pub(crate) fn cursor(&self) -> Pos2 { + pub(crate) fn placer(&self) -> &Placer { + &self.placer + } + + pub(crate) fn cursor(&self) -> Rect { self.placer.cursor() } + /// Where do we expect a zero-sized widget to be placed? + pub(crate) fn next_widget_position(&self) -> Pos2 { + self.placer.next_widget_position() + } + /// Allocated the given space and then adds content to that space. /// If the contents overflow, more space will be allocated. /// When finished, the amount of space actually used (`min_rect`) will be allocated. @@ -673,17 +685,15 @@ impl Ui { add_contents: impl FnOnce(&mut Self) -> R, ) -> InnerResponse { let item_spacing = self.spacing().item_spacing; - let outer_child_rect = self.placer.next_space(desired_size, item_spacing); - let inner_child_rect = self - .placer - .justify_and_align(outer_child_rect, desired_size); + let frame_rect = self.placer.next_space(desired_size, item_spacing); + let child_rect = self.placer.justify_and_align(frame_rect, desired_size); - let mut child_ui = self.child_ui(inner_child_rect, *self.layout()); + let mut child_ui = self.child_ui(child_rect, *self.layout()); let ret = add_contents(&mut child_ui); let final_child_rect = child_ui.min_rect(); self.placer.advance_after_rects( - outer_child_rect.union(final_child_rect), + frame_rect.union(final_child_rect), final_child_rect, item_spacing, ); @@ -740,8 +750,8 @@ impl Ui { /// }); /// ``` pub fn scroll_to_cursor(&mut self, align: Align) { - let scroll_y = self.cursor().y; - self.ctx().frame_state().scroll_target = Some((scroll_y, align)); + let target_y = self.next_widget_position().y; + self.ctx().frame_state().scroll_target = Some((target_y, align)); } } @@ -1094,8 +1104,7 @@ impl Ui { let child_rect = self.available_rect_before_wrap(); let mut child_ui = self.child_ui(child_rect, *self.layout()); let ret = add_contents(&mut child_ui); - let size = child_ui.min_size(); - let response = self.allocate_response(size, Sense::hover()); + let response = self.allocate_rect(child_ui.min_rect(), Sense::hover()); InnerResponse::new(ret, response) } @@ -1140,8 +1149,11 @@ impl Ui { "You can only indent vertical layouts, found {:?}", self.layout() ); - let indent = vec2(self.spacing().indent, 0.0); - let child_rect = Rect::from_min_max(self.cursor() + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts + + let indent = self.spacing().indent; + let mut child_rect = self.placer.available_rect_before_wrap(); + child_rect.min.x += indent; + let mut child_ui = Self { id: self.id.with(id_source), ..self.child_ui(child_rect, *self.layout()) @@ -1153,13 +1165,11 @@ impl Ui { child_ui.advance_cursor(4.0); } - let size = child_ui.min_size(); - // draw a faint line on the left to mark the indented section let stroke = self.visuals().widgets.noninteractive.bg_stroke; - let left_top = child_rect.min - indent * 0.5; + let left_top = child_rect.min - 0.5 * indent * Vec2::X; let left_top = self.painter().round_pos_to_pixels(left_top); - let left_bottom = pos2(left_top.x, left_top.y + size.y - 2.0); + let left_bottom = pos2(left_top.x, child_ui.min_rect().bottom() - 2.0); let left_bottom = self.painter().round_pos_to_pixels(left_bottom); self.painter.line_segment([left_top, left_bottom], stroke); if end_with_horizontal_line { @@ -1169,45 +1179,10 @@ impl Ui { .line_segment([left_bottom, right_bottom], stroke); } - let response = self.allocate_response(indent + size, Sense::hover()); + let response = self.allocate_rect(child_ui.min_rect(), Sense::hover()); InnerResponse::new(ret, response) } - #[deprecated] - pub fn left_column(&mut self, width: f32) -> Self { - #[allow(deprecated)] - self.column(Align::Min, width) - } - - #[deprecated] - pub fn centered_column(&mut self, width: f32) -> Self { - #[allow(deprecated)] - self.column(Align::Center, width) - } - - #[deprecated] - pub fn right_column(&mut self, width: f32) -> Self { - #[allow(deprecated)] - self.column(Align::Max, width) - } - - /// A column ui with a given width. - #[deprecated] - pub fn column(&mut self, column_position: Align, width: f32) -> Self { - let x = match column_position { - Align::Min => 0.0, - Align::Center => self.available_width() / 2.0 - width / 2.0, - Align::Max => self.available_width() - width, - }; - self.child_ui( - Rect::from_min_size( - self.cursor() + vec2(x, 0.0), - vec2(width, self.available_size_before_wrap().y), - ), - *self.layout(), - ) - } - /// Start a ui with horizontal layout. /// After you have called this, the function registers the contents as any other widget. /// @@ -1403,7 +1378,7 @@ impl Ui { let spacing = self.spacing().item_spacing.x; let total_spacing = spacing * (num_columns as f32 - 1.0); let column_width = (self.available_width() - total_spacing) / (num_columns as f32); - let top_left = self.cursor(); + let top_left = self.cursor().min; let mut columns: Vec = (0..num_columns) .map(|col_idx| { diff --git a/egui/src/widgets/label.rs b/egui/src/widgets/label.rs index c8633081..27e83361 100644 --- a/egui/src/widgets/label.rs +++ b/egui/src/widgets/label.rs @@ -263,7 +263,7 @@ impl Widget for Label { max_width, ); - let pos = pos2(ui.min_rect().left(), ui.cursor().y); + let pos = pos2(ui.min_rect().left(), ui.cursor().top()); assert!(!galley.rows.is_empty(), "Galleys are never empty"); let rect = galley.rows[0].rect().translate(vec2(pos.x, pos.y));