From bdc7127adfdb52a79459f13a37c93d367241f434 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 3 Feb 2022 15:25:47 +0100 Subject: [PATCH] Refactor flow, stack and grid layouters a bit --- src/geom/spec.rs | 8 +++ src/library/flow.rs | 6 +- src/library/grid.rs | 102 ++++++++++++++++++-------------- src/library/stack.rs | 137 ++++++++++++++++++++----------------------- 4 files changed, 131 insertions(+), 122 deletions(-) diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 1b8e13c2a..31f93a657 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -39,6 +39,14 @@ impl Spec { Spec { x: &self.x, y: &self.y } } + /// Convert from `&Spec` to `Spec<&::Target>`. + pub fn as_deref(&self) -> Spec<&T::Target> + where + T: Deref, + { + Spec { x: &self.x, y: &self.y } + } + /// Convert from `&mut Spec` to `Spec<&mut T>`. pub fn as_mut(&mut self) -> Spec<&mut T> { Spec { x: &mut self.x, y: &mut self.y } diff --git a/src/library/flow.rs b/src/library/flow.rs index 38f1b0e79..227a7a35d 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -190,11 +190,7 @@ impl<'a> FlowLayouter<'a> { let aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the // whole paragraph as it is itself aligned. - if node.is::() { - styles.get(ParNode::ALIGN) - } else { - Align::Left - }, + styles.get(ParNode::ALIGN), // Vertical align node alignment is respected by the flow node. node.downcast::() .and_then(|aligned| aligned.aligns.y) diff --git a/src/library/grid.rs b/src/library/grid.rs index d681ae7dc..59fe84865 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -40,12 +40,15 @@ impl Layout for GridNode { styles: StyleChain, ) -> Vec>> { // Prepare grid layout by unifying content and gutter tracks. - let mut layouter = GridLayouter::new(self, regions.clone(), styles); + let layouter = GridLayouter::new( + self.tracks.as_deref(), + self.gutter.as_deref(), + &self.children, + regions, + styles, + ); - // Determine all column sizes. - layouter.measure_columns(ctx); - - // Layout the grid row-by-row. + // Measure the columsna nd layout the grid row-by-row. layouter.layout(ctx) } } @@ -87,9 +90,9 @@ castable! { } /// Performs grid layout. -struct GridLayouter<'a> { - /// The children of the grid. - children: &'a [PackedNode], +pub struct GridLayouter<'a> { + /// The grid cells. + cells: &'a [PackedNode], /// The column tracks including gutter tracks. cols: Vec, /// The row tracks including gutter tracks. @@ -102,7 +105,7 @@ struct GridLayouter<'a> { rcols: Vec, /// Rows in the current region. lrows: Vec, - /// Whether the grid should expand to fill the region. + /// Whether the grid itself should expand to fill the region. expand: Spec, /// The full height of the current region. full: Length, @@ -127,19 +130,27 @@ enum Row { } impl<'a> GridLayouter<'a> { - /// Prepare grid layout by unifying content and gutter tracks. - fn new(grid: &'a GridNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { + /// Create a new grid layouter. + /// + /// This prepares grid layout by unifying content and gutter tracks. + pub fn new( + tracks: Spec<&[TrackSizing]>, + gutter: Spec<&[TrackSizing]>, + cells: &'a [PackedNode], + regions: &Regions, + styles: StyleChain<'a>, + ) -> 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); + let c = 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 len = cells.len(); + let given = tracks.y.len(); let needed = len / c + (len % c).clamp(0, 1); given.max(needed) }; @@ -152,14 +163,14 @@ impl<'a> GridLayouter<'a> { // 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)); + cols.push(get_or(tracks.x, x, auto)); + cols.push(get_or(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)); + rows.push(get_or(tracks.y, y, auto)); + rows.push(get_or(gutter.y, y, zero)); } // Remove superfluous gutter tracks. @@ -173,10 +184,11 @@ impl<'a> GridLayouter<'a> { // We use the regions for auto row measurement. Since at that moment, // columns are already sized, we can enable horizontal expansion. + let mut regions = regions.clone(); regions.expand = Spec::new(true, false); Self { - children: &grid.children, + cells, cols, rows, regions, @@ -192,6 +204,32 @@ impl<'a> GridLayouter<'a> { } } + /// Determines the columns sizes and then layouts the grid row-by-row. + pub fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { + self.measure_columns(ctx); + + for y in 0 .. self.rows.len() { + // Skip to next region if current one is full, but only for content + // rows, not for gutter rows. + if y % 2 == 0 && self.regions.is_full() { + self.finish_region(ctx); + } + + 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 + } + /// Determine all column sizes. fn measure_columns(&mut self, ctx: &mut LayoutContext) { enum Case { @@ -354,30 +392,6 @@ impl<'a> GridLayouter<'a> { } } - /// Layout the grid row-by-row. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { - for y in 0 .. self.rows.len() { - // Skip to next region if current one is full, but only for content - // rows, not for gutter rows. - if y % 2 == 0 && self.regions.is_full() { - self.finish_region(ctx); - } - - 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 height. Such a row may break across multiple /// regions. fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { @@ -599,7 +613,7 @@ impl<'a> GridLayouter<'a> { // 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) + self.cells.get((y / 2) * c + x / 2) } else { None } diff --git a/src/library/stack.rs b/src/library/stack.rs index 394b0a38b..0ff8e9731 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -32,7 +32,29 @@ impl Layout for StackNode { regions: &Regions, styles: StyleChain, ) -> Vec>> { - StackLayouter::new(self, regions.clone(), styles).layout(ctx) + let mut layouter = StackLayouter::new(self.dir, regions); + + // Spacing to insert before the next node. + let mut deferred = None; + + for child in &self.children { + match child { + StackChild::Spacing(kind) => { + layouter.layout_spacing(*kind); + deferred = None; + } + StackChild::Node(node) => { + if let Some(kind) = deferred { + layouter.layout_spacing(kind); + } + + layouter.layout_node(ctx, node, styles); + deferred = self.spacing; + } + } + } + + layouter.finish() } } @@ -65,29 +87,23 @@ castable! { } /// Performs stack layout. -struct StackLayouter<'a> { - /// The children of the stack. - children: &'a [StackChild], +pub struct StackLayouter { /// The stacking direction. dir: Dir, /// The axis of the stacking direction. axis: SpecAxis, - /// The spacing between non-spacing children. - spacing: Option, /// The regions to layout children into. regions: Regions, - /// The inherited styles. - styles: StyleChain<'a>, - /// Whether the stack should expand to fill the region. + /// Whether the stack itself should expand to fill the region. expand: Spec, - /// The full size of `regions.current` that was available before we started - /// subtracting. + /// The full size of the current region that was available at the start. full: Size, /// The generic size used by the frames for the current region. used: Gen, /// The sum of fractional ratios in the current region. fr: Fractional, - /// Spacing and layouted nodes. + /// Already layouted items whose exact positions are not yet known due to + /// fractional spacing. items: Vec, /// Finished frames for previous regions. finished: Vec>>, @@ -103,24 +119,21 @@ enum StackItem { Frame(Arc, Align), } -impl<'a> StackLayouter<'a> { +impl StackLayouter { /// Create a new stack layouter. - fn new(stack: &'a StackNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { - let dir = stack.dir; + pub fn new(dir: Dir, regions: &Regions) -> Self { let axis = dir.axis(); let expand = regions.expand; let full = regions.current; // Disable expansion along the block axis for children. + let mut regions = regions.clone(); regions.expand.set(axis, false); Self { - children: &stack.children, dir, axis, - spacing: stack.spacing, regions, - styles, expand, full, used: Gen::zero(), @@ -130,67 +143,43 @@ impl<'a> StackLayouter<'a> { } } - /// Layout all children. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { - // Spacing to insert before the next node. - let mut deferred = None; - - for child in self.children { - match *child { - StackChild::Spacing(kind) => { - self.layout_spacing(kind); - deferred = None; - } - StackChild::Node(ref node) => { - if let Some(kind) = deferred { - self.layout_spacing(kind); - } - - if self.regions.is_full() { - self.finish_region(); - } - - self.layout_node(ctx, node); - deferred = self.spacing; - } - } - } - - self.finish_region(); - self.finished - } - - /// Layout spacing. - fn layout_spacing(&mut self, spacing: SpacingKind) { + /// Add spacing along the spacing direction. + pub fn layout_spacing(&mut self, spacing: SpacingKind) { match spacing { - SpacingKind::Linear(v) => self.layout_absolute(v), + SpacingKind::Linear(v) => { + // Resolve the linear and limit it to the remaining space. + let resolved = v.resolve(self.regions.base.get(self.axis)); + let remaining = self.regions.current.get_mut(self.axis); + let limited = resolved.min(*remaining); + *remaining -= limited; + self.used.main += limited; + self.items.push(StackItem::Absolute(resolved)); + } SpacingKind::Fractional(v) => { - self.items.push(StackItem::Fractional(v)); self.fr += v; + self.items.push(StackItem::Fractional(v)); } } } - /// Layout absolute spacing. - fn layout_absolute(&mut self, amount: Linear) { - // Resolve the linear, limiting it to the remaining available space. - let remaining = self.regions.current.get_mut(self.axis); - let resolved = amount.resolve(self.regions.base.get(self.axis)); - let limited = resolved.min(*remaining); - *remaining -= limited; - self.used.main += limited; - self.items.push(StackItem::Absolute(resolved)); - } + /// Layout an arbitrary node. + pub fn layout_node( + &mut self, + ctx: &mut LayoutContext, + node: &PackedNode, + styles: StyleChain, + ) { + if self.regions.is_full() { + self.finish_region(); + } - /// Layout a node. - fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { // Align nodes' block-axis alignment is respected by the stack node. let align = node .downcast::() .and_then(|node| node.aligns.get(self.axis)) .unwrap_or(self.dir.start().into()); - let frames = node.layout(ctx, &self.regions, self.styles); + let frames = node.layout(ctx, &self.regions, styles); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. @@ -206,8 +195,8 @@ impl<'a> StackLayouter<'a> { } } - /// Finish the frame for one region. - fn finish_region(&mut self) { + /// Advance to the next region. + pub fn finish_region(&mut self) { // Determine the size of the stack in this region dependening on whether // the region expands. let used = self.used.to_spec(self.axis); @@ -228,12 +217,8 @@ impl<'a> StackLayouter<'a> { // Place all frames. for item in self.items.drain(..) { match item { - StackItem::Absolute(v) => { - cursor += v; - } - StackItem::Fractional(v) => { - cursor += v.resolve(self.fr, remaining); - } + StackItem::Absolute(v) => cursor += v, + StackItem::Fractional(v) => cursor += v.resolve(self.fr, remaining), StackItem::Frame(frame, align) => { if self.dir.is_positive() { ruler = ruler.max(align); @@ -270,4 +255,10 @@ impl<'a> StackLayouter<'a> { self.fr = Fractional::zero(); self.finished.push(output.constrain(cts)); } + + /// Finish layouting and return the resulting frames. + pub fn finish(mut self) -> Vec>> { + self.finish_region(); + self.finished + } }