diff options
Diffstat (limited to 'src/layout/grid.rs')
| -rw-r--r-- | src/layout/grid.rs | 551 |
1 files changed, 0 insertions, 551 deletions
diff --git a/src/layout/grid.rs b/src/layout/grid.rs deleted file mode 100644 index 57986b48..00000000 --- a/src/layout/grid.rs +++ /dev/null @@ -1,551 +0,0 @@ -use super::*; - -/// A node that arranges its children in a grid. -#[derive(Debug, Hash)] -pub struct GridNode { - /// Defines sizing for content rows and columns. - pub tracks: Spec<Vec<TrackSizing>>, - /// Defines sizing of gutter rows and columns between content. - pub gutter: Spec<Vec<TrackSizing>>, - /// The nodes to be arranged in a grid. - pub children: Vec<BlockNode>, -} - -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum TrackSizing { - /// Fit the cell to its contents. - Auto, - /// A length stated in absolute values and/or relative to the parent's size. - Linear(Linear), - /// A length that is the fraction of the remaining free space in the parent. - Fractional(Fractional), -} - -impl BlockLevel for GridNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - // Prepare grid layout by unifying content and gutter tracks. - let mut layouter = GridLayouter::new(self, regions.clone()); - - // Determine all column sizes. - layouter.measure_columns(ctx); - - // Layout the grid row-by-row. - layouter.layout(ctx) - } -} - -/// Performs grid layout. -struct GridLayouter<'a> { - /// The original expand state of the target region. - expand: Spec<bool>, - /// The column tracks including gutter tracks. - cols: Vec<TrackSizing>, - /// The row tracks including gutter tracks. - rows: Vec<TrackSizing>, - /// The children of the grid. - children: &'a [BlockNode], - /// The regions to layout into. - regions: Regions, - /// Resolved column sizes. - rcols: Vec<Length>, - /// The full block size of the current region. - full: Length, - /// The used-up size of the current region. The horizontal size is - /// determined once after columns are resolved and not touched again. - used: Size, - /// The sum of fractional ratios in the current region. - fr: Fractional, - /// Rows in the current region. - lrows: Vec<Row>, - /// Constraints for the active region. - cts: Constraints, - /// Frames for finished regions. - finished: Vec<Constrained<Rc<Frame>>>, -} - -/// Produced by initial row layout, auto and linear rows are already finished, -/// fractional rows not yet. -enum Row { - /// Finished row frame of auto or linear row. - Frame(Frame), - /// Ratio of a fractional row and y index of the track. - Fr(Fractional, usize), -} - -impl<'a> GridLayouter<'a> { - /// Prepare grid layout by unifying content and gutter tracks. - fn new(grid: &'a GridNode, mut regions: Regions) -> Self { - let mut cols = vec![]; - let mut rows = vec![]; - - // Number of content columns: Always at least one. - let c = grid.tracks.x.len().max(1); - - // Number of content rows: At least as many as given, but also at least - // as many as needed to place each item. - let r = { - let len = grid.children.len(); - let given = grid.tracks.y.len(); - let needed = len / c + (len % c).clamp(0, 1); - given.max(needed) - }; - - let auto = TrackSizing::Auto; - let zero = TrackSizing::Linear(Linear::zero()); - let get_or = |tracks: &[_], idx, default| { - tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) - }; - - // Collect content and gutter columns. - for x in 0 .. c { - cols.push(get_or(&grid.tracks.x, x, auto)); - cols.push(get_or(&grid.gutter.x, x, zero)); - } - - // Collect content and gutter rows. - for y in 0 .. r { - rows.push(get_or(&grid.tracks.y, y, auto)); - rows.push(get_or(&grid.gutter.y, y, zero)); - } - - // Remove superfluous gutter tracks. - cols.pop(); - rows.pop(); - - // We use the regions only for auto row measurement and constraints. - let expand = regions.expand; - regions.expand = Spec::new(true, false); - - Self { - children: &grid.children, - cts: Constraints::new(expand), - full: regions.current.h, - expand, - rcols: vec![Length::zero(); cols.len()], - lrows: vec![], - used: Size::zero(), - fr: Fractional::zero(), - finished: vec![], - cols, - rows, - regions, - } - } - - /// Determine all column sizes. - fn measure_columns(&mut self, ctx: &mut LayoutContext) { - enum Case { - PurelyLinear, - Fitting, - Exact, - Overflowing, - } - - // The different cases affecting constraints. - let mut case = Case::PurelyLinear; - - // Sum of sizes of resolved linear tracks. - let mut linear = Length::zero(); - - // Sum of fractions of all fractional tracks. - let mut fr = Fractional::zero(); - - // Resolve the size of all linear columns and compute the sum of all - // fractional tracks. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - match col { - TrackSizing::Auto => { - case = Case::Fitting; - } - TrackSizing::Linear(v) => { - let resolved = v.resolve(self.regions.base.w); - *rcol = resolved; - linear += resolved; - } - TrackSizing::Fractional(v) => { - case = Case::Fitting; - fr += v; - } - } - } - - // Size that is not used by fixed-size columns. - let available = self.regions.current.w - linear; - if available >= Length::zero() { - // Determine size of auto columns. - let (auto, count) = self.measure_auto_columns(ctx, available); - - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Length::zero() { - if !fr.is_zero() { - self.grow_fractional_columns(remaining, fr); - case = Case::Exact; - } - } else { - self.shrink_auto_columns(available, count); - case = Case::Exact; - } - } else if matches!(case, Case::Fitting) { - case = Case::Overflowing; - } - - // Children could depend on base. - self.cts.base = self.regions.base.to_spec().map(Some); - - // Set constraints depending on the case we hit. - match case { - Case::PurelyLinear => {} - Case::Fitting => self.cts.min.x = Some(self.used.w), - Case::Exact => self.cts.exact.x = Some(self.regions.current.w), - Case::Overflowing => self.cts.max.x = Some(linear), - } - - // Sum up the resolved column sizes once here. - self.used.w = self.rcols.iter().sum(); - } - - /// Measure the size that is available to auto columns. - fn measure_auto_columns( - &mut self, - ctx: &mut LayoutContext, - available: Length, - ) -> (Length, usize) { - let mut auto = Length::zero(); - let mut count = 0; - - // Determine size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, &col) in self.cols.iter().enumerate() { - if col != TrackSizing::Auto { - continue; - } - - let mut resolved = Length::zero(); - for y in 0 .. self.rows.len() { - if let Some(node) = self.cell(x, y) { - let size = Size::new(available, Length::inf()); - let mut regions = - Regions::one(size, self.regions.base, Spec::splat(false)); - - // For fractional rows, we can already resolve the correct - // base, for auto it's already correct and for fr we could - // only guess anyway. - if let TrackSizing::Linear(v) = self.rows[y] { - regions.base.h = v.resolve(self.regions.base.h); - } - - let frame = node.layout(ctx, ®ions).remove(0).item; - resolved.set_max(frame.size.w); - } - } - - self.rcols[x] = resolved; - auto += resolved; - count += 1; - } - - (auto, count) - } - - /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Length, fr: Fractional) { - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let TrackSizing::Fractional(v) = col { - let ratio = v / fr; - if ratio.is_finite() { - *rcol = ratio * remaining; - } - } - } - } - - /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Length, count: usize) { - // The fair share each auto column may have. - let fair = available / count as f64; - - // The number of overlarge auto columns and the space that will be - // equally redistributed to them. - let mut overlarge: usize = 0; - let mut redistribute = available; - - // Find out the number of and space used by overlarge auto columns. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto { - if *rcol > fair { - overlarge += 1; - } else { - redistribute -= *rcol; - } - } - } - - // Redistribute the space equally. - let share = redistribute / overlarge as f64; - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto && *rcol > fair { - *rcol = share; - } - } - } - - /// Layout the grid row-by-row. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { - for y in 0 .. self.rows.len() { - match self.rows[y] { - TrackSizing::Auto => self.layout_auto_row(ctx, y), - TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y), - TrackSizing::Fractional(v) => { - self.cts.exact.y = Some(self.full); - self.lrows.push(Row::Fr(v, y)); - self.fr += v; - } - } - } - - self.finish_region(ctx); - self.finished - } - - /// Layout a row with automatic size along the block axis. Such a row may - /// break across multiple regions. - fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { - let mut resolved: Vec<Length> = vec![]; - - // Determine the size for each region of the row. - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - let mut regions = self.regions.clone(); - regions.mutate(|size| size.w = rcol); - - // Set the horizontal base back to the parent region's base for - // auto columns. - if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; - } - - let mut sizes = - node.layout(ctx, ®ions).into_iter().map(|frame| frame.item.size.h); - - for (target, size) in resolved.iter_mut().zip(&mut sizes) { - target.set_max(size); - } - - resolved.extend(sizes); - } - } - - // Nothing to layout. - if resolved.is_empty() { - return; - } - - // Layout into a single region. - if let &[first] = resolved.as_slice() { - let frame = self.layout_single_row(ctx, first, y); - self.push_row(frame); - return; - } - - // Expand all but the last region if the space is not - // eaten up by any fr rows. - if self.fr.is_zero() { - let len = resolved.len(); - for (target, (current, _)) in - resolved[.. len - 1].iter_mut().zip(self.regions.iter()) - { - target.set_max(current.h); - } - } - - // Layout into multiple regions. - let frames = self.layout_multi_row(ctx, &resolved, y); - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { - self.push_row(frame); - if i + 1 < len { - self.cts.exact.y = Some(self.full); - self.finish_region(ctx); - } - } - } - - /// Layout a row with linear sizing along the block axis. Such a row cannot - /// break across multiple regions, but it may force a region break. - fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { - let resolved = v.resolve(self.regions.base.h); - let frame = self.layout_single_row(ctx, resolved, y); - - // Skip to fitting region. - let length = frame.size.h; - while !self.regions.current.h.fits(length) && !self.regions.in_full_last() { - self.cts.max.y = Some(self.used.h + length); - self.finish_region(ctx); - - // Don't skip multiple regions for gutter and don't push a row. - if y % 2 == 1 { - return; - } - } - - self.push_row(frame); - } - - /// Layout a row with a fixed size along the block axis and return its frame. - fn layout_single_row( - &self, - ctx: &mut LayoutContext, - height: Length, - y: usize, - ) -> Frame { - let mut output = Frame::new(Size::new(self.used.w, height), height); - let mut pos = Point::zero(); - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - let size = Size::new(rcol, height); - - // Set the base to the size for non-auto rows. - let mut base = self.regions.base; - if self.cols[x] != TrackSizing::Auto { - base.w = size.w; - } - if self.rows[y] != TrackSizing::Auto { - base.h = size.h; - } - - let regions = Regions::one(size, base, Spec::splat(true)); - let frame = node.layout(ctx, ®ions).remove(0); - output.push_frame(pos, frame.item); - } - - pos.x += rcol; - } - - output - } - - /// Layout a row spanning multiple regions. - fn layout_multi_row( - &self, - ctx: &mut LayoutContext, - resolved: &[Length], - y: usize, - ) -> Vec<Frame> { - // Prepare frames. - let mut outputs: Vec<_> = resolved - .iter() - .map(|&h| Frame::new(Size::new(self.used.w, h), h)) - .collect(); - - // Prepare regions. - let size = Size::new(self.used.w, resolved[0]); - let mut regions = Regions::one(size, self.regions.base, Spec::splat(true)); - regions.backlog = resolved[1 ..] - .iter() - .map(|&h| Size::new(self.used.w, h)) - .collect::<Vec<_>>() - .into_iter(); - - // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - regions.mutate(|size| size.w = rcol); - - // Set the horizontal base back to the parent region's base for - // auto columns. - if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; - } - - // Push the layouted frames into the individual output frames. - let frames = node.layout(ctx, ®ions); - for (output, frame) in outputs.iter_mut().zip(frames) { - output.push_frame(pos, frame.item); - } - } - - pos.x += rcol; - } - - outputs - } - - /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame) { - self.regions.current.h -= frame.size.h; - self.used.h += frame.size.h; - self.lrows.push(Row::Frame(frame)); - } - - /// Finish rows for one region. - fn finish_region(&mut self, ctx: &mut LayoutContext) { - // Determine the size that remains for fractional rows. - let remaining = self.full - self.used.h; - - // Determine the size of the grid in this region, expanding fully if - // there are fr rows. - let mut size = self.used; - if !self.fr.is_zero() && self.full.is_finite() { - size.h = self.full; - } - - self.cts.min.y = Some(size.h); - - // The frame for the region. - let mut output = Frame::new(size, size.h); - let mut pos = Point::zero(); - - // Place finished rows and layout fractional rows. - for row in std::mem::take(&mut self.lrows) { - let frame = match row { - Row::Frame(frame) => frame, - Row::Fr(v, y) => { - let ratio = v / self.fr; - if remaining.is_finite() && ratio.is_finite() { - let resolved = ratio * remaining; - self.layout_single_row(ctx, resolved, y) - } else { - continue; - } - } - }; - - let height = frame.size.h; - output.merge_frame(pos, frame); - pos.y += height; - } - - self.regions.next(); - self.full = self.regions.current.h; - self.used.h = Length::zero(); - self.fr = Fractional::zero(); - self.finished.push(output.constrain(self.cts)); - self.cts = Constraints::new(self.expand); - } - - /// Get the node in the cell in column `x` and row `y`. - /// - /// Returns `None` if it's a gutter cell. - #[track_caller] - fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> { - assert!(x < self.cols.len()); - assert!(y < self.rows.len()); - - // Even columns and rows are children, odd ones are gutter. - if x % 2 == 0 && y % 2 == 0 { - let c = 1 + self.cols.len() / 2; - self.children.get((y / 2) * c + x / 2) - } else { - None - } - } -} |
