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 {
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)
})

View file

@ -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),
};
}

View file

@ -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)
}
}

View file

@ -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<R>(
&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<R>(
&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()))
}