Auto-shrink grid

This commit is contained in:
Emil Ernerfeldt 2021-01-13 23:00:14 +01:00
parent 4ebaa53fea
commit 63d3e9e70b
4 changed files with 72 additions and 63 deletions

View file

@ -51,8 +51,15 @@ impl State {
pub(crate) struct GridLayout { pub(crate) struct GridLayout {
ctx: CtxRef, ctx: CtxRef,
id: Id, 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, spacing: Vec2,
striped: bool, striped: bool,
initial_x: f32, initial_x: f32,
default_row_height: f32, default_row_height: f32,
@ -62,10 +69,13 @@ pub(crate) struct GridLayout {
impl GridLayout { impl GridLayout {
pub(crate) fn new(ui: &Ui, id: Id) -> Self { pub(crate) fn new(ui: &Ui, id: Id) -> Self {
let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default();
Self { Self {
ctx: ui.ctx().clone(), ctx: ui.ctx().clone(),
id, id,
state: ui.memory().grid.get(&id).cloned().unwrap_or_default(), prev_state,
curr_state: State::default(),
spacing: ui.style().spacing.item_spacing, spacing: ui.style().spacing.item_spacing,
striped: false, striped: false,
initial_x: ui.cursor().x, initial_x: ui.cursor().x,
@ -74,20 +84,11 @@ impl GridLayout {
row: 0, 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 { impl GridLayout {
fn row_height(&self, row: usize) -> f32 { fn prev_row_height(&self, row: usize) -> f32 {
self.state self.prev_state
.row_height(row) .row_height(row)
.unwrap_or(self.default_row_height) .unwrap_or(self.default_row_height)
} }
@ -101,25 +102,33 @@ impl GridLayout {
} }
pub(crate) fn next_cell(&self, cursor: Pos2, child_size: Vec2) -> Rect { 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 width = self.prev_state.col_width(self.col).unwrap_or(0.0);
let height = self.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, size) Rect::from_min_size(cursor, size)
} }
pub(crate) fn advance(&mut self, cursor: &mut Pos2, rect: Rect) { pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) {
let dirty = self.state.set_min_col_width(self.col, rect.width()); let dirty = self
let dirty = self.state.set_min_row_height(self.row, rect.height()) || dirty; .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 { 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.ctx.request_repaint();
} }
self.col += 1; 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) { 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.x = self.initial_x;
cursor.y += row_height + self.spacing.y; cursor.y += row_height + self.spacing.y;
@ -127,9 +136,9 @@ impl GridLayout {
self.row += 1; self.row += 1;
if self.striped && self.row % 2 == 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: // 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 rect = Rect::from_min_size(*cursor, size);
let color = Rgba::from_white_alpha(0.0075); let color = Rgba::from_white_alpha(0.0075);
// let color = Rgba::from_black_alpha(0.2); // let color = Rgba::from_black_alpha(0.2);
@ -184,7 +193,10 @@ impl Grid {
ui.wrap(|ui| { ui.wrap(|ui| {
let id = ui.make_persistent_id(id_source); 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); ui.set_grid(grid);
add_contents(ui) add_contents(ui)
}) })

View file

@ -357,9 +357,9 @@ impl Layout {
} }
/// Returns where to put the next widget that is of the given size. /// 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. /// The returned `frame_rect` `Rect` will always be justified along the cross axis.
/// This is what you then pass to `advance_after_outer_rect`. /// This is what you then pass to `advance_after_rects`.
/// Use `justify_or_align` to get the inner `Rect`. /// Use `justify_or_align` to get the inner `widget_rect`.
#[allow(clippy::collapsible_if)] #[allow(clippy::collapsible_if)]
pub(crate) fn next_space( pub(crate) fn next_space(
&self, &self,
@ -456,20 +456,21 @@ impl Layout {
} }
/// Advance cursor after a widget was added to a specific rectangle. /// 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. /// * `frame_rect`: the frame inside which a widget was e.g. centered
pub(crate) fn advance_after_outer_rect( /// * `widget_rect`: the actual rect used by the widget
pub(crate) fn advance_after_rects(
&self, &self,
region: &mut Region, region: &mut Region,
outer_rect: Rect, frame_rect: Rect,
inner_rect: Rect, widget_rect: Rect,
item_spacing: Vec2, item_spacing: Vec2,
) { ) {
region.cursor = match self.main_dir { region.cursor = match self.main_dir {
Direction::LeftToRight => pos2(inner_rect.right() + item_spacing.x, outer_rect.top()), Direction::LeftToRight => pos2(widget_rect.right() + item_spacing.x, frame_rect.top()),
Direction::RightToLeft => pos2(inner_rect.left() - item_spacing.x, outer_rect.top()), Direction::RightToLeft => pos2(widget_rect.left() - item_spacing.x, frame_rect.top()),
Direction::TopDown => pos2(outer_rect.left(), inner_rect.bottom() + item_spacing.y), Direction::TopDown => pos2(frame_rect.left(), widget_rect.bottom() + item_spacing.y),
Direction::BottomUp => pos2(outer_rect.left(), inner_rect.top() - item_spacing.y), Direction::BottomUp => pos2(frame_rect.left(), widget_rect.top() - item_spacing.y),
}; };
} }

View file

@ -82,9 +82,9 @@ impl Placer {
} }
/// Returns where to put the next widget that is of the given size. /// 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. /// The returned `frame_rect` will always be justified along the cross axis.
/// This is what you then pass to `advance_after_outer_rect`. /// This is what you then pass to `advance_after_rects`.
/// Use `justify_or_align` to get the inner `Rect`. /// Use `justify_or_align` to get the inner `widget_rect`.
pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect { pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect {
if let Some(grid) = &self.grid { if let Some(grid) = &self.grid {
grid.next_cell(self.region.cursor, child_size) 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. /// 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. /// * `frame_rect`: the frame inside which a widget was e.g. centered
pub(crate) fn advance_after_outer_rect( /// * `widget_rect`: the actual rect used by the widget
pub(crate) fn advance_after_rects(
&mut self, &mut self,
outer_rect: Rect, frame_rect: Rect,
inner_rect: Rect, widget_rect: Rect,
item_spacing: Vec2, item_spacing: Vec2,
) { ) {
if let Some(grid) = &mut self.grid { 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 { } else {
self.layout.advance_after_outer_rect( self.layout
&mut self.region, .advance_after_rects(&mut self.region, frame_rect, widget_rect, item_spacing)
outer_rect,
inner_rect,
item_spacing,
)
} }
} }

View file

@ -507,20 +507,19 @@ impl Ui {
/// Returns where to put the widget. /// Returns where to put the widget.
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect { fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
let item_spacing = self.style().spacing.item_spacing; let item_spacing = self.style().spacing.item_spacing;
let outer_child_rect = self.placer.next_space(desired_size, item_spacing); let frame_rect = self.placer.next_space(desired_size, item_spacing);
let inner_child_rect = self.placer.justify_or_align(outer_child_rect, desired_size); let widget_rect = self.placer.justify_or_align(frame_rect, desired_size);
self.placer self.placer
.advance_after_outer_rect(outer_child_rect, inner_child_rect, item_spacing); .advance_after_rects(frame_rect, widget_rect, item_spacing);
self.expand_to_include_rect(inner_child_rect); self.expand_to_include_rect(widget_rect);
inner_child_rect widget_rect
} }
pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id {
let item_spacing = self.style().spacing.item_spacing; let item_spacing = self.style().spacing.item_spacing;
self.placer self.placer.advance_after_rects(rect, rect, item_spacing);
.advance_after_outer_rect(rect, rect, item_spacing);
self.expand_to_include_rect(rect); self.expand_to_include_rect(rect);
self.next_auto_id = self.next_auto_id.wrapping_add(1); 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 ret = add_contents(&mut child_ui);
let final_child_rect = child_ui.min_rect(); 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), outer_child_rect.union(final_child_rect),
final_child_rect, final_child_rect,
item_spacing, item_spacing,
@ -1075,15 +1074,16 @@ impl Ui {
} }
/// Start a ui with vertical layout. /// Start a ui with vertical layout.
/// Widgets will be centered. /// Widgets will be horizontally centered.
pub fn vertical_centered<R>( pub fn vertical_centered<R>(
&mut self, &mut self,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Ui) -> R,
) -> (R, Response) { ) -> (R, Response) {
self.with_layout(Layout::top_down(Align::Center), add_contents) self.with_layout(Layout::top_down(Align::Center), add_contents)
} }
/// Start a ui with vertical layout. /// 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<R>( pub fn vertical_centered_justified<R>(
&mut self, &mut self,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Ui) -> R,
@ -1103,8 +1103,7 @@ impl Ui {
let ret = add_contents(&mut child_ui); let ret = add_contents(&mut child_ui);
let rect = child_ui.min_rect(); let rect = child_ui.min_rect();
let item_spacing = self.style().spacing.item_spacing; let item_spacing = self.style().spacing.item_spacing;
self.placer self.placer.advance_after_rects(rect, rect, item_spacing);
.advance_after_outer_rect(rect, rect, item_spacing);
self.expand_to_include_rect(rect); self.expand_to_include_rect(rect);
(ret, self.interact(rect, child_ui.id, Sense::hover())) (ret, self.interact(rect, child_ui.id, Sense::hover()))
} }