From 63d3e9e70bde6d45a51810b8eae10e692c359ce4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 13 Jan 2021 23:00:14 +0100 Subject: [PATCH] Auto-shrink grid --- egui/src/grid.rs | 60 +++++++++++++++++++++++++++------------------- egui/src/layout.rs | 25 +++++++++---------- egui/src/placer.rs | 27 ++++++++++----------- egui/src/ui.rs | 23 +++++++++--------- 4 files changed, 72 insertions(+), 63 deletions(-) diff --git a/egui/src/grid.rs b/egui/src/grid.rs index d774d52f..83a7bd23 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -51,8 +51,15 @@ impl State { pub(crate) struct GridLayout { ctx: CtxRef, id: Id, - state: State, + + /// State previous frame (if any). + /// This can be used to predict future sizes of cells. + prev_state: State, + /// State accumulated during the current frame. + curr_state: State, + spacing: Vec2, + striped: bool, initial_x: f32, default_row_height: f32, @@ -62,10 +69,13 @@ pub(crate) struct GridLayout { impl GridLayout { pub(crate) fn new(ui: &Ui, id: Id) -> Self { + let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default(); + Self { ctx: ui.ctx().clone(), id, - state: ui.memory().grid.get(&id).cloned().unwrap_or_default(), + prev_state, + curr_state: State::default(), spacing: ui.style().spacing.item_spacing, striped: false, initial_x: ui.cursor().x, @@ -74,20 +84,11 @@ impl GridLayout { row: 0, } } - - /// If `true`, add a subtle background color to every other row. - /// - /// This can make a table easier to read. - /// Default: `false`. - pub(crate) fn striped(mut self, striped: bool) -> Self { - self.striped = striped; - self - } } impl GridLayout { - fn row_height(&self, row: usize) -> f32 { - self.state + fn prev_row_height(&self, row: usize) -> f32 { + self.prev_state .row_height(row) .unwrap_or(self.default_row_height) } @@ -101,25 +102,33 @@ impl GridLayout { } pub(crate) fn next_cell(&self, cursor: Pos2, child_size: Vec2) -> Rect { - let width = self.state.col_width(self.col).unwrap_or(0.0); - let height = self.row_height(self.row); + 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) } - pub(crate) fn advance(&mut self, cursor: &mut Pos2, rect: Rect) { - let dirty = self.state.set_min_col_width(self.col, rect.width()); - let dirty = self.state.set_min_row_height(self.row, rect.height()) || dirty; + pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) { + let dirty = self + .curr_state + .set_min_col_width(self.col, widget_rect.width()); + let dirty = self + .curr_state + .set_min_row_height(self.row, widget_rect.height()) + || dirty; if dirty { - self.ctx.memory().grid.insert(self.id, self.state.clone()); + self.ctx + .memory() + .grid + .insert(self.id, self.curr_state.clone()); self.ctx.request_repaint(); } self.col += 1; - cursor.x += rect.width() + self.spacing.x; + cursor.x += frame_rect.width() + self.spacing.x; } pub(crate) fn end_row(&mut self, cursor: &mut Pos2, painter: &Painter) { - let row_height = self.row_height(self.row); + let row_height = self.prev_row_height(self.row); cursor.x = self.initial_x; cursor.y += row_height + self.spacing.y; @@ -127,9 +136,9 @@ impl GridLayout { self.row += 1; if self.striped && self.row % 2 == 1 { - if let Some(height) = self.state.row_height(self.row) { + if let Some(height) = self.prev_state.row_height(self.row) { // Paint background for coming row: - let size = Vec2::new(self.state.full_width(self.spacing.x), height); + let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height); let rect = Rect::from_min_size(*cursor, size); let color = Rgba::from_white_alpha(0.0075); // let color = Rgba::from_black_alpha(0.2); @@ -184,7 +193,10 @@ impl Grid { ui.wrap(|ui| { let id = ui.make_persistent_id(id_source); - let grid = GridLayout::new(ui, id).striped(striped); + let grid = GridLayout { + striped, + ..GridLayout::new(ui, id) + }; ui.set_grid(grid); add_contents(ui) }) diff --git a/egui/src/layout.rs b/egui/src/layout.rs index c99d73ea..bb936ab5 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -357,9 +357,9 @@ impl Layout { } /// Returns where to put the next widget that is of the given size. - /// The returned "outer" `Rect` will always be justified along the cross axis. - /// This is what you then pass to `advance_after_outer_rect`. - /// Use `justify_or_align` to get the inner `Rect`. + /// 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_or_align` to get the inner `widget_rect`. #[allow(clippy::collapsible_if)] pub(crate) fn next_space( &self, @@ -456,20 +456,21 @@ impl Layout { } /// Advance cursor after a widget was added to a specific rectangle. - /// `outer_rect` is a hack needed because the Vec2 cursor is not quite sufficient to keep track - /// of what is happening when we are doing wrapping layouts. - pub(crate) fn advance_after_outer_rect( + /// + /// * `frame_rect`: the frame inside which a widget was e.g. centered + /// * `widget_rect`: the actual rect used by the widget + pub(crate) fn advance_after_rects( &self, region: &mut Region, - outer_rect: Rect, - inner_rect: Rect, + frame_rect: Rect, + widget_rect: Rect, item_spacing: Vec2, ) { region.cursor = match self.main_dir { - Direction::LeftToRight => pos2(inner_rect.right() + item_spacing.x, outer_rect.top()), - Direction::RightToLeft => pos2(inner_rect.left() - item_spacing.x, outer_rect.top()), - Direction::TopDown => pos2(outer_rect.left(), inner_rect.bottom() + item_spacing.y), - Direction::BottomUp => pos2(outer_rect.left(), inner_rect.top() - item_spacing.y), + 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), }; } diff --git a/egui/src/placer.rs b/egui/src/placer.rs index 2fbe2642..ed2d7f9a 100644 --- a/egui/src/placer.rs +++ b/egui/src/placer.rs @@ -82,9 +82,9 @@ impl Placer { } /// Returns where to put the next widget that is of the given size. - /// The returned "outer" `Rect` will always be justified along the cross axis. - /// This is what you then pass to `advance_after_outer_rect`. - /// Use `justify_or_align` to get the inner `Rect`. + /// The returned `frame_rect` will always be justified along the cross axis. + /// This is what you then pass to `advance_after_rects`. + /// Use `justify_or_align` to get the inner `widget_rect`. pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect { if let Some(grid) = &self.grid { grid.next_cell(self.region.cursor, child_size) @@ -109,23 +109,20 @@ impl Placer { } /// Advance cursor after a widget was added to a specific rectangle. - /// `outer_rect` is a hack needed because the Vec2 cursor is not quite sufficient to keep track - /// of what is happening when we are doing wrapping layouts. - pub(crate) fn advance_after_outer_rect( + /// + /// * `frame_rect`: the frame inside which a widget was e.g. centered + /// * `widget_rect`: the actual rect used by the widget + pub(crate) fn advance_after_rects( &mut self, - outer_rect: Rect, - inner_rect: Rect, + frame_rect: Rect, + widget_rect: Rect, item_spacing: Vec2, ) { if let Some(grid) = &mut self.grid { - grid.advance(&mut self.region.cursor, outer_rect) + grid.advance(&mut self.region.cursor, frame_rect, widget_rect) } else { - self.layout.advance_after_outer_rect( - &mut self.region, - outer_rect, - inner_rect, - item_spacing, - ) + self.layout + .advance_after_rects(&mut self.region, frame_rect, widget_rect, item_spacing) } } diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 75d73540..6e7c259a 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -507,20 +507,19 @@ impl Ui { /// Returns where to put the widget. fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect { let item_spacing = self.style().spacing.item_spacing; - let outer_child_rect = self.placer.next_space(desired_size, item_spacing); - let inner_child_rect = self.placer.justify_or_align(outer_child_rect, desired_size); + let frame_rect = self.placer.next_space(desired_size, item_spacing); + let widget_rect = self.placer.justify_or_align(frame_rect, desired_size); self.placer - .advance_after_outer_rect(outer_child_rect, inner_child_rect, item_spacing); - self.expand_to_include_rect(inner_child_rect); + .advance_after_rects(frame_rect, widget_rect, item_spacing); + self.expand_to_include_rect(widget_rect); - inner_child_rect + widget_rect } pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { let item_spacing = self.style().spacing.item_spacing; - self.placer - .advance_after_outer_rect(rect, rect, item_spacing); + self.placer.advance_after_rects(rect, rect, item_spacing); self.expand_to_include_rect(rect); self.next_auto_id = self.next_auto_id.wrapping_add(1); @@ -548,7 +547,7 @@ impl Ui { let ret = add_contents(&mut child_ui); let final_child_rect = child_ui.min_rect(); - self.placer.advance_after_outer_rect( + self.placer.advance_after_rects( outer_child_rect.union(final_child_rect), final_child_rect, item_spacing, @@ -1075,15 +1074,16 @@ impl Ui { } /// Start a ui with vertical layout. - /// Widgets will be centered. + /// Widgets will be horizontally centered. pub fn vertical_centered( &mut self, add_contents: impl FnOnce(&mut Ui) -> R, ) -> (R, Response) { self.with_layout(Layout::top_down(Align::Center), add_contents) } + /// Start a ui with vertical layout. - /// Widgets will be centered and justified (fill full width). + /// Widgets will be horizontally centered and justified (fill full width). pub fn vertical_centered_justified( &mut self, add_contents: impl FnOnce(&mut Ui) -> R, @@ -1103,8 +1103,7 @@ impl Ui { let ret = add_contents(&mut child_ui); let rect = child_ui.min_rect(); let item_spacing = self.style().spacing.item_spacing; - self.placer - .advance_after_outer_rect(rect, rect, item_spacing); + self.placer.advance_after_rects(rect, rect, item_spacing); self.expand_to_include_rect(rect); (ret, self.interact(rect, child_ui.id, Sense::hover())) }