summaryrefslogtreecommitdiff
path: root/src/layout/grid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout/grid.rs')
-rw-r--r--src/layout/grid.rs398
1 files changed, 192 insertions, 206 deletions
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index 9c4c2e0e..3ba6c16b 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -1,216 +1,128 @@
-use std::usize;
-
use super::*;
-use crate::library::GridUnits;
-/// A node that stacks its children.
+/// A node that arranges its children in a grid.
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct GridNode {
- /// The `main` and `cross` directions of this stack.
- ///
- /// The children are stacked along the `main` direction. The `cross`
- /// direction is required for aligning the children.
- pub dir: Dir,
- /// The nodes to be stacked.
+ /// The column (cross) direction of this stack.
+ pub column_dir: Dir,
+ /// The nodes to be arranged in a grid.
pub children: Vec<AnyNode>,
- pub tracks: Gen<GridUnits>,
- pub gutter: Gen<GridUnits>,
+ /// Defines sizing for rows and columns.
+ pub tracks: Gen<Tracks>,
+ /// Defines sizing of the gutter between rows and columns.
+ pub gutter: Gen<Tracks>,
}
impl Layout for GridNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
- let layout = GridLayouter::new(self, regions).layout(ctx);
- layout
+ GridLayouter::new(self, regions.clone()).layout(ctx)
}
}
-#[derive(Debug)]
-enum GridItem<'a> {
- Node(&'a AnyNode),
- Gutter,
+impl From<GridNode> for AnyNode {
+ fn from(grid: GridNode) -> Self {
+ Self::new(grid)
+ }
}
-#[derive(Debug)]
struct GridLayouter<'a> {
- items: Vec<GridItem<'a>>,
+ cross: SpecAxis,
+ main: SpecAxis,
cols: Vec<TrackSizing>,
rows: Vec<TrackSizing>,
- region: Regions,
- dir: Dir,
+ cells: Vec<Cell<'a>>,
+ regions: Regions,
rrows: Vec<(usize, Option<Length>)>,
rcols: Vec<Length>,
- frames: Vec<Frame>,
+ finished: Vec<Frame>,
+}
+
+enum Cell<'a> {
+ Node(&'a AnyNode),
+ Gutter,
}
impl<'a> GridLayouter<'a> {
- fn new(
- grid: &'a GridNode,
- regions: &Regions,
- ) -> Self {
- let mut items = vec![];
+ fn new(grid: &'a GridNode, regions: Regions) -> Self {
let mut col_sizes = vec![];
let mut row_sizes = vec![];
- let cols = grid.tracks.cross.0.len();
- // Create at least as many rows as specified and a row to fit every item.
- let rows = if cols > 0 {
- let res = grid
- .tracks
- .main
- .0
- .len()
- .max(grid.children.len() / cols + (grid.children.len() % cols).clamp(0, 1));
- res
- } else {
- 0
+ let mut cells = vec![];
+
+ // A grid always needs to have at least one column.
+ let cols = grid.tracks.cross.0.len().max(1);
+
+ // Create at least as many rows as specified and also at least as many
+ // as necessary to place each item.
+ let rows = {
+ let len = grid.children.len();
+ let specified = grid.tracks.main.0.len();
+ let necessary = len / cols + (len % cols).clamp(0, 1);
+ specified.max(necessary)
};
- for (i, col_size) in grid.tracks.cross.0.iter().enumerate() {
- let last = i == cols - 1;
- col_sizes.push(*col_size);
-
- if !last {
- let gutter = grid.gutter.cross.get(i);
- col_sizes.push(gutter);
+ // Collect the track sizing for all columns, including gutter columns.
+ for i in 0 .. cols {
+ col_sizes.push(grid.tracks.cross.get(i));
+ if i < cols - 1 {
+ col_sizes.push(grid.gutter.cross.get(i));
}
}
- for (i, row_size) in (0 .. rows).map(|i| (i, grid.tracks.main.get(i))) {
- let last = i == rows - 1;
- row_sizes.push(row_size);
-
- if !last {
- let gutter = grid.gutter.main.get(i);
- row_sizes.push(gutter);
+ // Collect the track sizing for all rows, including gutter rows.
+ for i in 0 .. rows {
+ row_sizes.push(grid.tracks.main.get(i));
+ if i < rows - 1 {
+ row_sizes.push(grid.gutter.main.get(i));
}
}
+ // Build up the matrix of cells, including gutter cells.
for (i, item) in grid.children.iter().enumerate() {
- if cols == 0 {
- break;
- }
+ cells.push(Cell::Node(item));
let row = i / cols;
let col = i % cols;
- items.push(GridItem::Node(item));
-
- if col != cols - 1 {
- // Push gutter
- items.push(GridItem::Gutter);
- } else if row != rows - 1 {
- // Push gutter row.
+ if col < cols - 1 {
+ // Push gutter after each child.
+ cells.push(Cell::Gutter);
+ } else if row < rows - 1 {
+ // Except for the last child of each row.
+ // There we push a gutter row.
for _ in 0 .. col_sizes.len() {
- items.push(GridItem::Gutter);
+ cells.push(Cell::Gutter);
}
}
}
- // Fill the thing up
- while items.len() < col_sizes.len() * row_sizes.len() {
- items.push(GridItem::Gutter)
+ // Fill the thing up.
+ while cells.len() < col_sizes.len() * row_sizes.len() {
+ cells.push(Cell::Gutter)
}
- GridLayouter {
+ Self {
+ cross: grid.column_dir.axis(),
+ main: grid.column_dir.axis().other(),
cols: col_sizes,
rows: row_sizes,
- region: regions.clone(),
- dir: grid.dir,
- items,
+ cells,
+ regions,
rrows: vec![],
rcols: vec![],
- frames: vec![],
- }
- }
-
- fn get(&self, x: usize, y: usize) -> &GridItem<'_> {
- assert!(x < self.cols.len());
- assert!(y < self.rows.len());
- let row_cmp = y * self.cols.len();
-
- self.items.get(row_cmp + x).unwrap()
- }
-
- fn main(&self) -> SpecAxis {
- self.dir.axis().other()
- }
-
- fn cross(&self) -> SpecAxis {
- self.dir.axis()
- }
-
- fn finish_region(&mut self, ctx: &mut LayoutContext, total_frs: f64) {
- let mut pos = Gen::splat(Length::zero());
- let pos2point = |mut pos: Gen<Length>| {
- if !self.dir.is_positive() {
- pos.cross = -pos.cross;
- }
- pos.switch(self.main()).to_point()
- };
- let mut frame = Frame::new(Size::zero(), Length::zero());
- let mut total_cross = Length::zero();
- let mut total_main = Length::zero();
-
- for (x, &w) in self.rcols.iter().enumerate() {
- let total: Length = self.rrows.iter().filter_map(|(_, x)| *x).sum();
- let available = self.region.current.get(self.main()) - total;
- total_cross += w;
-
- for (y, h) in self.rrows.iter() {
- let element = self.get(x, *y);
- let h = if let Some(len) = h {
- *len
- } else {
- if let TrackSizing::Fractional(f) = self.rows[*y] {
- if total_frs > 0.0 {
- let res = available * (f.get() / total_frs);
- if res.is_finite() {
- res
- } else {
- Length::zero()
- }
- } else {
- Length::zero()
- }
- } else {
- unreachable!()
- }
- };
- if x == 0 {
- total_main += h;
- }
-
- if let GridItem::Node(n) = element {
- let item = n.layout(ctx, &Regions::one(Gen::new(w, h).switch(self.main()).to_size(), Spec::splat(false))).remove(0);
- frame.push_frame(pos2point(pos), item);
- }
-
- pos.main += h;
- }
- pos.main = Length::zero();
- pos.cross += self.dir.factor() as f64 * w;
+ finished: vec![],
}
-
- if !self.dir.is_positive() {
- frame.translate(Gen::new(total_cross, Length::zero()).switch(self.main()).to_point());
- }
-
- frame.size = Gen::new(total_cross, total_main).switch(self.main()).to_size();
- frame.baseline = frame.size.height;
-
- self.frames.push(frame);
-
- self.rrows.clear();
- self.region.next();
}
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
// Shrink area by linear sizing.
- let mut available = self.region.current.get(self.cross());
+ let mut available = self.regions.current.get(self.cross);
available -= self
.cols
.iter()
.filter_map(|x| match x {
- TrackSizing::Linear(l) => Some(l.resolve(self.region.base.get(self.cross()))),
+ TrackSizing::Linear(l) => {
+ Some(l.resolve(self.regions.base.get(self.cross)))
+ }
_ => None,
})
.sum();
@@ -232,34 +144,31 @@ impl<'a> GridLayouter<'a> {
let mut col_width = vec![];
- // For each of the auto columns, lay out all elements with `preliminary_length`
- // rows and build max.
+ // For each of the auto columns, lay out all elements with
+ // `preliminary_length` rows and build max.
for x in auto_columns {
let mut max = Length::zero();
- for (y, row_height) in
- self.rows.iter().enumerate().map(|(y, s)| {
- (y, s.preliminary_length(self.region.base.get(self.main())))
- })
- {
- let item = self.get(x, y);
- let size =
- Gen::new(self.region.current.get(self.cross()), row_height).switch(self.main()).to_size();
+
+ for (y, row) in self.rows.iter().enumerate() {
+ let mut size = self.regions.current;
+ if let TrackSizing::Linear(l) = row {
+ *size.get_mut(self.main) =
+ l.resolve(self.regions.base.get(self.main));
+ }
+
let region = Regions::one(size, Spec::splat(false));
- match item {
- GridItem::Node(n) => {
- max = max.max(
- n.layout(ctx, &region).first().unwrap().size.get(self.cross()),
- )
- }
- GridItem::Gutter => {}
+ if let Cell::Node(node) = self.get(x, y) {
+ let frame = node.layout(ctx, &region).remove(0);
+ max = max.max(frame.size.get(self.cross))
}
}
col_width.push((x, max));
}
- // If accumulated auto column size exceeds available size, redistribute space
- // proportionally amongst elements that exceed their size allocation.
+ // If accumulated auto column size exceeds available size, redistribute
+ // space proportionally amongst elements that exceed their size
+ // allocation.
let mut total: Length = col_width.iter().map(|(_, x)| *x).sum();
if total > available {
let alloc = available / col_width.len() as f64;
@@ -288,23 +197,25 @@ impl<'a> GridLayouter<'a> {
}
// Build rcols
- for (x, len) in col_width.into_iter().map(|(x, s)| (x, Some(s))).chain(std::iter::once((self.cols.len(), None))) {
+ for (x, len) in col_width
+ .into_iter()
+ .map(|(x, s)| (x, Some(s)))
+ .chain(std::iter::once((self.cols.len(), None)))
+ {
for i in self.rcols.len() .. x {
let len = match self.cols[i] {
- TrackSizing::Linear(l) => l.resolve(self.region.base.get(self.cross())),
+ TrackSizing::Linear(l) => {
+ l.resolve(self.regions.base.get(self.cross))
+ }
TrackSizing::Fractional(f) => {
if col_frac == 0.0 {
Length::zero()
} else {
let res: Length = (available - total) * (f.get() / col_frac);
- if res.is_finite() {
- res
- } else {
- Length::zero()
- }
+ if res.is_finite() { res } else { Length::zero() }
}
}
- TrackSizing::Auto => unreachable!(),
+ TrackSizing::Auto => unreachable!("x is an auto track"),
};
self.rcols.push(len);
@@ -317,27 +228,23 @@ impl<'a> GridLayouter<'a> {
// Determine non-`fr` row heights
let mut total_frs = 0.0;
- let mut current = self.region.current.get(self.main());
+ let mut current = self.regions.current.get(self.main);
- for y in 0..self.rows.len() {
- let height = &self.rows[y];
- let resolved = match height {
- TrackSizing::Linear(l) => Some(l.resolve(self.region.base.get(self.main()))),
+ for y in 0 .. self.rows.len() {
+ let resolved = match self.rows[y] {
+ TrackSizing::Linear(l) => {
+ Some(l.resolve(self.regions.base.get(self.main)))
+ }
TrackSizing::Auto => {
let mut max = Length::zero();
for (x, len) in self.rcols.iter().enumerate() {
- let node = self.get(x, y);
- if let GridItem::Node(node) = node {
- let frames = node.layout(
- ctx,
- &Regions::one(
- Gen::new(*len, current)
- .switch(self.main())
- .to_size(),
- Spec::splat(false),
- ),
+ if let Cell::Node(node) = self.get(x, y) {
+ let regions = Regions::one(
+ Gen::new(*len, current).to_size(self.main),
+ Spec::splat(false),
);
- max = max.max(frames.first().unwrap().size.get(self.main()));
+ let frame = node.layout(ctx, &regions).remove(0);
+ max = max.max(frame.size.get(self.main));
}
}
Some(max)
@@ -345,13 +252,13 @@ impl<'a> GridLayouter<'a> {
TrackSizing::Fractional(f) => {
total_frs += f.get();
None
- },
+ }
};
if let Some(resolved) = resolved {
- while !current.fits(resolved) && !self.region.in_full_last() {
+ while !current.fits(resolved) && !self.regions.in_full_last() {
self.finish_region(ctx, total_frs);
- current = self.region.current.get(self.main());
+ current = self.regions.current.get(self.main);
total_frs = 0.0;
}
current -= resolved;
@@ -361,12 +268,91 @@ impl<'a> GridLayouter<'a> {
}
self.finish_region(ctx, total_frs);
- self.frames
+ self.finished
+ }
+
+ fn finish_region(&mut self, ctx: &mut LayoutContext, total_frs: f64) {
+ let mut pos = Gen::splat(Length::zero());
+ let mut frame = Frame::new(Size::zero(), Length::zero());
+ let mut total_cross = Length::zero();
+ let mut total_main = Length::zero();
+
+ for (x, &w) in self.rcols.iter().enumerate() {
+ let total: Length = self.rrows.iter().filter_map(|(_, x)| *x).sum();
+ let available = self.regions.current.get(self.main) - total;
+ total_cross += w;
+
+ for (y, h) in self.rrows.iter() {
+ let element = self.get(x, *y);
+ let h = if let Some(len) = h {
+ *len
+ } else if let TrackSizing::Fractional(f) = self.rows[*y] {
+ if total_frs > 0.0 {
+ let res = available * (f.get() / total_frs);
+ if res.is_finite() { res } else { Length::zero() }
+ } else {
+ Length::zero()
+ }
+ } else {
+ unreachable!("non-fractional tracks are already resolved");
+ };
+
+ if x == 0 {
+ total_main += h;
+ }
+
+ if let Cell::Node(n) = element {
+ let regions = Regions::one(
+ Gen::new(w, h).to_size(self.main),
+ Spec::splat(false),
+ );
+ let item = n.layout(ctx, &regions).remove(0);
+ frame.push_frame(pos.to_point(self.main), item);
+ }
+
+ pos.main += h;
+ }
+ pos.main = Length::zero();
+ pos.cross += w;
+ }
+
+ frame.size = Gen::new(total_cross, total_main).to_size(self.main);
+ frame.baseline = frame.size.height;
+
+ self.rrows.clear();
+ self.regions.next();
+ self.finished.push(frame);
+ }
+
+ fn get(&self, x: usize, y: usize) -> &Cell<'a> {
+ assert!(x < self.cols.len());
+ assert!(y < self.rows.len());
+ self.cells.get(y * self.cols.len() + x).unwrap()
}
}
-impl From<GridNode> for AnyNode {
- fn from(grid: GridNode) -> Self {
- Self::new(grid)
+/// A list of track sizing definitions.
+#[derive(Default, Debug, Clone, PartialEq, Hash)]
+pub struct Tracks(pub Vec<TrackSizing>);
+
+impl Tracks {
+ /// Get the sizing for the track at the given `idx`.
+ fn get(&self, idx: usize) -> TrackSizing {
+ self.0
+ .get(idx)
+ .or(self.0.last())
+ .copied()
+ .unwrap_or(TrackSizing::Auto)
}
}
+
+/// Defines how to size a grid cell along an axis.
+#[derive(Debug, Copy, Clone, PartialEq, Hash)]
+pub enum TrackSizing {
+ /// Fit the cell to its contents.
+ Auto,
+ /// A length stated in absolute values and fractions of the parent's size.
+ Linear(Linear),
+ /// A length that is the fraction of the remaining free space in the parent.
+ Fractional(Fractional),
+}