summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-03 15:25:47 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-03 15:25:47 +0100
commitbdc7127adfdb52a79459f13a37c93d367241f434 (patch)
tree6a2cd121155b00bd4b43c15fffe7060f890a2375
parentf9d380249295280ebf84900d726c3baca565d511 (diff)
Refactor flow, stack and grid layouters a bit
-rw-r--r--src/geom/spec.rs8
-rw-r--r--src/library/flow.rs6
-rw-r--r--src/library/grid.rs102
-rw-r--r--src/library/stack.rs137
4 files changed, 131 insertions, 122 deletions
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 1b8e13c2..31f93a65 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -39,6 +39,14 @@ impl<T> Spec<T> {
Spec { x: &self.x, y: &self.y }
}
+ /// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`.
+ pub fn as_deref(&self) -> Spec<&T::Target>
+ where
+ T: Deref,
+ {
+ Spec { x: &self.x, y: &self.y }
+ }
+
/// Convert from `&mut Spec<T>` 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 38f1b0e7..227a7a35 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::<ParNode>() {
- styles.get(ParNode::ALIGN)
- } else {
- Align::Left
- },
+ styles.get(ParNode::ALIGN),
// Vertical align node alignment is respected by the flow node.
node.downcast::<AlignNode>()
.and_then(|aligned| aligned.aligns.y)
diff --git a/src/library/grid.rs b/src/library/grid.rs
index d681ae7d..59fe8486 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -40,12 +40,15 @@ impl Layout for GridNode {
styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> {
// Prepare grid layout by unifying content and gutter tracks.
- let mut layouter = GridLayouter::new(self, regions.clone(), styles);
-
- // Determine all column sizes.
- layouter.measure_columns(ctx);
+ let layouter = GridLayouter::new(
+ self.tracks.as_deref(),
+ self.gutter.as_deref(),
+ &self.children,
+ regions,
+ styles,
+ );
- // 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<TrackSizing>,
/// The row tracks including gutter tracks.
@@ -102,7 +105,7 @@ struct GridLayouter<'a> {
rcols: Vec<Length>,
/// Rows in the current region.
lrows: Vec<Row>,
- /// Whether the grid should expand to fill the region.
+ /// Whether the grid itself should expand to fill the region.
expand: Spec<bool>,
/// 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<Constrained<Arc<Frame>>> {
+ 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<Constrained<Arc<Frame>>> {
- 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 394b0a38..0ff8e973 100644
--- a/src/library/stack.rs
+++ b/src/library/stack.rs
@@ -32,7 +32,29 @@ impl Layout for StackNode {
regions: &Regions,
styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> {
- 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<SpacingKind>,
/// 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<bool>,
- /// 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<Length>,
/// 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<StackItem>,
/// Finished frames for previous regions.
finished: Vec<Constrained<Arc<Frame>>>,
@@ -103,24 +119,21 @@ enum StackItem {
Frame(Arc<Frame>, 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<Constrained<Arc<Frame>>> {
- // 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::<AlignNode>()
.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<Constrained<Arc<Frame>>> {
+ self.finish_region();
+ self.finished
+ }
}