diff options
Diffstat (limited to 'src/layout/grid.rs')
| -rw-r--r-- | src/layout/grid.rs | 398 |
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, ®ion).first().unwrap().size.get(self.cross()), - ) - } - GridItem::Gutter => {} + if let Cell::Node(node) = self.get(x, y) { + let frame = node.layout(ctx, ®ion).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, ®ions).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, ®ions).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), +} |
