diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/frame.rs | 8 | ||||
| -rw-r--r-- | src/layout/grid.rs | 398 | ||||
| -rw-r--r-- | src/layout/stack.rs | 165 |
3 files changed, 288 insertions, 283 deletions
diff --git a/src/layout/frame.rs b/src/layout/frame.rs index f1dc07e6..6cecc7a3 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -35,14 +35,6 @@ impl Frame { self.push(pos + subpos, element); } } - - /// Translate the positions of all elements in the frame by adding the - /// argument to their position. - pub fn translate(&mut self, amount: Point) { - for (pos, _) in &mut self.elements { - *pos += amount; - } - } } /// The building block frames are composed of. 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), +} diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 410f53c6..e4c0708d 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -29,24 +29,7 @@ pub enum StackChild { impl Layout for StackNode { fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { - let mut layouter = StackLayouter::new(self.dirs, self.aspect, regions.clone()); - for child in &self.children { - match *child { - StackChild::Spacing(amount) => layouter.push_spacing(amount), - StackChild::Any(ref node, aligns) => { - let mut frames = node.layout(ctx, &layouter.regions).into_iter(); - if let Some(frame) = frames.next() { - layouter.push_frame(frame, aligns); - } - - for frame in frames { - layouter.finish_region(); - layouter.push_frame(frame, aligns); - } - } - } - } - layouter.finish() + StackLayouter::new(self, regions.clone()).layout(ctx) } } @@ -56,107 +39,154 @@ impl From<StackNode> for AnyNode { } } -struct StackLayouter { - dirs: Gen<Dir>, - aspect: Option<N64>, +struct StackLayouter<'a> { + /// The directions of the stack. + stack: &'a StackNode, + /// The axis of the main direction. main: SpecAxis, + /// The region to layout into. regions: Regions, - finished: Vec<Frame>, - frames: Vec<(Length, Frame, Gen<Align>)>, + /// Offset, alignment and frame for all children that fit into the current + /// region. The exact positions are not known yet. + frames: Vec<(Length, Gen<Align>, Frame)>, + /// The full size of `regions.current` that was available before we started + /// subtracting. full: Size, - size: Gen<Length>, + /// The generic size used by the frames for the current region. + used: Gen<Length>, + /// The alignment ruler for the current region. ruler: Align, + /// Finished frames for previous regions. + finished: Vec<Frame>, } -impl StackLayouter { - fn new(dirs: Gen<Dir>, aspect: Option<N64>, mut regions: Regions) -> Self { - if let Some(aspect) = aspect { +impl<'a> StackLayouter<'a> { + fn new(stack: &'a StackNode, mut regions: Regions) -> Self { + if let Some(aspect) = stack.aspect { regions.apply_aspect_ratio(aspect); } Self { - dirs, - aspect, - main: dirs.main.axis(), + stack, + main: stack.dirs.main.axis(), finished: vec![], frames: vec![], full: regions.current, - size: Gen::zero(), + used: Gen::zero(), ruler: Align::Start, regions, } } + fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> { + for child in &self.stack.children { + match *child { + StackChild::Spacing(amount) => self.push_spacing(amount), + StackChild::Any(ref node, aligns) => { + let mut frames = node.layout(ctx, &self.regions).into_iter(); + if let Some(frame) = frames.next() { + self.push_frame(frame, aligns); + } + + for frame in frames { + self.finish_region(); + self.push_frame(frame, aligns); + } + } + } + } + + self.finish_region(); + self.finished + } + fn push_spacing(&mut self, amount: Length) { + // Cap the spacing to the remaining available space. let remaining = self.regions.current.get_mut(self.main); let capped = amount.min(*remaining); + + // Grow our size and shrink the available space in the region. + self.used.main += capped; *remaining -= capped; - self.size.main += capped; } fn push_frame(&mut self, frame: Frame, aligns: Gen<Align>) { + let size = frame.size; + + // Don't allow `Start` after `End` in the same region. if self.ruler > aligns.main { self.finish_region(); } - while !self.regions.current.fits(frame.size) && !self.regions.in_full_last() { + // Adjust the ruler. + self.ruler = aligns.main; + + // Find a fitting region. + while !self.regions.current.fits(size) && !self.regions.in_full_last() { self.finish_region(); } - let offset = self.size.main; - let size = frame.size.switch(self.main); - self.size.main += size.main; - self.size.cross.set_max(size.cross); - self.ruler = aligns.main; - *self.regions.current.get_mut(self.main) -= size.main; - self.frames.push((offset, frame, aligns)); + // Remember the frame with offset and alignment. + self.frames.push((self.used.main, aligns, frame)); + + // Grow our size and shrink available space in the region. + let gen = size.to_gen(self.main); + self.used.main += gen.main; + self.used.cross.set_max(gen.cross); + *self.regions.current.get_mut(self.main) -= gen.main; } fn finish_region(&mut self) { + let used = self.used.to_size(self.main); let fixed = self.regions.fixed; - let used = self.size.switch(self.main).to_size(); - let mut size = Size::new( + // Determine the stack's size dependening on whether the region is + // fixed. + let mut stack_size = Size::new( if fixed.horizontal { self.full.width } else { used.width }, if fixed.vertical { self.full.height } else { used.height }, ); - if let Some(aspect) = self.aspect { - let width = size + // Make sure the stack's size satisfies the aspect ratio. + if let Some(aspect) = self.stack.aspect { + let width = stack_size .width - .max(aspect.into_inner() * size.height) + .max(aspect.into_inner() * stack_size.height) .min(self.full.width) .min(aspect.into_inner() * self.full.height); - size = Size::new(width, width / aspect.into_inner()); + stack_size = Size::new(width, width / aspect.into_inner()); } - let mut output = Frame::new(size, size.height); + let mut output = Frame::new(stack_size, stack_size.height); let mut first = true; - let used = self.size; - let size = size.switch(self.main); - - for (offset, frame, aligns) in std::mem::take(&mut self.frames) { - let child = frame.size.switch(self.main); + // Place all frames. + for (offset, aligns, frame) in std::mem::take(&mut self.frames) { + let stack_size = stack_size.to_gen(self.main); + let child_size = frame.size.to_gen(self.main); // Align along the cross axis. - let cross = aligns - .cross - .resolve(self.dirs.cross, Length::zero() .. size.cross - child.cross); + let cross = aligns.cross.resolve( + self.stack.dirs.cross, + Length::zero() .. stack_size.cross - child_size.cross, + ); // Align along the main axis. let main = aligns.main.resolve( - self.dirs.main, - if self.dirs.main.is_positive() { - offset .. size.main - used.main + offset + self.stack.dirs.main, + if self.stack.dirs.main.is_positive() { + offset .. stack_size.main - self.used.main + offset } else { - let offset_with_self = offset + child.main; - used.main - offset_with_self .. size.main - offset_with_self + let offset_with_self = offset + child_size.main; + self.used.main - offset_with_self + .. stack_size.main - offset_with_self }, ); - let pos = Gen::new(cross, main).switch(self.main).to_point(); + let pos = Gen::new(cross, main).to_point(self.main); + + // The baseline of the stack is that of the first frame. if first { output.baseline = pos.y + frame.baseline; first = false; @@ -165,18 +195,15 @@ impl StackLayouter { output.push_frame(pos, frame); } - self.size = Gen::zero(); - self.ruler = Align::Start; + // Move on to the next region. self.regions.next(); - if let Some(aspect) = self.aspect { + if let Some(aspect) = self.stack.aspect { self.regions.apply_aspect_ratio(aspect); } + self.full = self.regions.current; + self.used = Gen::zero(); + self.ruler = Align::Start; self.finished.push(output); } - - fn finish(mut self) -> Vec<Frame> { - self.finish_region(); - self.finished - } } |
