Refactor flow, stack and grid layouters a bit

This commit is contained in:
Laurenz 2022-02-03 15:25:47 +01:00
parent f9d3802492
commit bdc7127adf
4 changed files with 131 additions and 122 deletions

View File

@ -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 }

View File

@ -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)

View File

@ -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);
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<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
}

View File

@ -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
}
}