Remove directions from grid

This commit is contained in:
Laurenz 2021-10-16 12:22:47 +02:00
parent 6690bc2354
commit c627847cb3
4 changed files with 81 additions and 161 deletions

View File

@ -4,12 +4,10 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(feature = "layout-cache", derive(Hash))] #[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct GridNode { pub struct GridNode {
/// The inline (columns) and block (rows) directions of this grid.
pub dirs: Gen<Dir>,
/// Defines sizing for content rows and columns. /// Defines sizing for content rows and columns.
pub tracks: Gen<Vec<TrackSizing>>, pub tracks: Spec<Vec<TrackSizing>>,
/// Defines sizing of gutter rows and columns between content. /// Defines sizing of gutter rows and columns between content.
pub gutter: Gen<Vec<TrackSizing>>, pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid. /// The nodes to be arranged in a grid.
pub children: Vec<LayoutNode>, pub children: Vec<LayoutNode>,
} }
@ -50,10 +48,6 @@ impl From<GridNode> for LayoutNode {
/// Performs grid layout. /// Performs grid layout.
struct GridLayouter<'a> { struct GridLayouter<'a> {
/// The axis of the inline direction.
inline: SpecAxis,
/// The axis of the block direction.
block: SpecAxis,
/// The original expand state of the target region. /// The original expand state of the target region.
expand: Spec<bool>, expand: Spec<bool>,
/// The column tracks including gutter tracks. /// The column tracks including gutter tracks.
@ -68,15 +62,15 @@ struct GridLayouter<'a> {
rcols: Vec<Length>, rcols: Vec<Length>,
/// The full block size of the current region. /// The full block size of the current region.
full: Length, full: Length,
/// The used-up size of the current region. The inline size is determined /// The used-up size of the current region. The horizontal size is
/// once after columns are resolved and not touched again. /// determined once after columns are resolved and not touched again.
used: Gen<Length>, used: Size,
/// The sum of fractional ratios in the current region. /// The sum of fractional ratios in the current region.
fr: Fractional, fr: Fractional,
/// Rows in the current region. /// Rows in the current region.
lrows: Vec<Row>, lrows: Vec<Row>,
/// Constraints for the active region. /// Constraints for the active region.
constraints: Constraints, cts: Constraints,
/// Frames for finished regions. /// Frames for finished regions.
finished: Vec<Constrained<Rc<Frame>>>, finished: Vec<Constrained<Rc<Frame>>>,
} }
@ -97,13 +91,13 @@ impl<'a> GridLayouter<'a> {
let mut rows = vec![]; let mut rows = vec![];
// Number of content columns: Always at least one. // Number of content columns: Always at least one.
let c = grid.tracks.inline.len().max(1); let c = grid.tracks.x.len().max(1);
// Number of content rows: At least as many as given, but also at least // Number of content rows: At least as many as given, but also at least
// as many as needed to place each item. // as many as needed to place each item.
let r = { let r = {
let len = grid.children.len(); let len = grid.children.len();
let given = grid.tracks.block.len(); let given = grid.tracks.y.len();
let needed = len / c + (len % c).clamp(0, 1); let needed = len / c + (len % c).clamp(0, 1);
given.max(needed) given.max(needed)
}; };
@ -116,42 +110,38 @@ impl<'a> GridLayouter<'a> {
// Collect content and gutter columns. // Collect content and gutter columns.
for x in 0 .. c { for x in 0 .. c {
cols.push(get_or(&grid.tracks.inline, x, auto)); cols.push(get_or(&grid.tracks.x, x, auto));
cols.push(get_or(&grid.gutter.inline, x, zero)); cols.push(get_or(&grid.gutter.x, x, zero));
} }
// Collect content and gutter rows. // Collect content and gutter rows.
for y in 0 .. r { for y in 0 .. r {
rows.push(get_or(&grid.tracks.block, y, auto)); rows.push(get_or(&grid.tracks.y, y, auto));
rows.push(get_or(&grid.gutter.block, y, zero)); rows.push(get_or(&grid.gutter.y, y, zero));
} }
// Remove superfluous gutter tracks. // Remove superfluous gutter tracks.
cols.pop(); cols.pop();
rows.pop(); rows.pop();
let inline = grid.dirs.inline.axis(); let full = regions.current.h;
let block = grid.dirs.block.axis();
let full = regions.current.get(block);
let rcols = vec![Length::zero(); cols.len()]; let rcols = vec![Length::zero(); cols.len()];
// We use the regions only for auto row measurement and constraints. // We use the regions only for auto row measurement and constraints.
let expand = regions.expand; let expand = regions.expand;
regions.expand = Gen::new(true, false).to_spec(block); regions.expand = Spec::new(true, false);
Self { Self {
inline,
block,
cols, cols,
rows, rows,
children: &grid.children, children: &grid.children,
constraints: Constraints::new(expand), cts: Constraints::new(expand),
regions, regions,
expand, expand,
rcols, rcols,
lrows: vec![], lrows: vec![],
full, full,
used: Gen::zero(), used: Size::zero(),
fr: Fractional::zero(), fr: Fractional::zero(),
finished: vec![], finished: vec![],
} }
@ -166,10 +156,6 @@ impl<'a> GridLayouter<'a> {
Overflowing, Overflowing,
} }
// Generic version of current and base size.
let current = self.regions.current.get(self.inline);
let base = self.regions.base.get(self.inline);
// The different cases affecting constraints. // The different cases affecting constraints.
let mut case = Case::PurelyLinear; let mut case = Case::PurelyLinear;
@ -187,7 +173,7 @@ impl<'a> GridLayouter<'a> {
case = Case::Fitting; case = Case::Fitting;
} }
TrackSizing::Linear(v) => { TrackSizing::Linear(v) => {
let resolved = v.resolve(base); let resolved = v.resolve(self.regions.base.w);
*rcol = resolved; *rcol = resolved;
linear += resolved; linear += resolved;
} }
@ -199,7 +185,7 @@ impl<'a> GridLayouter<'a> {
} }
// Size that is not used by fixed-size columns. // Size that is not used by fixed-size columns.
let available = current - linear; let available = self.regions.current.w - linear;
if available >= Length::zero() { if available >= Length::zero() {
// Determine size of auto columns. // Determine size of auto columns.
let (auto, count) = self.measure_auto_columns(ctx, available); let (auto, count) = self.measure_auto_columns(ctx, available);
@ -221,24 +207,18 @@ impl<'a> GridLayouter<'a> {
} }
// Children could depend on base. // Children could depend on base.
self.constraints.base = self.regions.base.to_spec().map(Some); self.cts.base = self.regions.base.to_spec().map(Some);
// Set constraints depending on the case we hit. // Set constraints depending on the case we hit.
match case { match case {
Case::PurelyLinear => {} Case::PurelyLinear => {}
Case::Fitting => { Case::Fitting => self.cts.min.x = Some(self.used.w),
self.constraints.min.set(self.inline, Some(self.used.inline)); Case::Exact => self.cts.exact.x = Some(self.regions.current.w),
} Case::Overflowing => self.cts.max.x = Some(linear),
Case::Exact => {
self.constraints.exact.set(self.inline, Some(current));
}
Case::Overflowing => {
self.constraints.max.set(self.inline, Some(linear));
}
} }
// Sum up the resolved column sizes once here. // Sum up the resolved column sizes once here.
self.used.inline = self.rcols.iter().sum(); self.used.w = self.rcols.iter().sum();
} }
/// Measure the size that is available to auto columns. /// Measure the size that is available to auto columns.
@ -247,8 +227,6 @@ impl<'a> GridLayouter<'a> {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
available: Length, available: Length,
) -> (Length, usize) { ) -> (Length, usize) {
let base = self.regions.base.get(self.block);
let mut auto = Length::zero(); let mut auto = Length::zero();
let mut count = 0; let mut count = 0;
@ -262,7 +240,7 @@ impl<'a> GridLayouter<'a> {
let mut resolved = Length::zero(); let mut resolved = Length::zero();
for y in 0 .. self.rows.len() { for y in 0 .. self.rows.len() {
if let Some(node) = self.cell(x, y) { if let Some(node) = self.cell(x, y) {
let size = Gen::new(available, Length::inf()).to_size(self.block); let size = Size::new(available, Length::inf());
let mut regions = let mut regions =
Regions::one(size, self.regions.base, Spec::splat(false)); Regions::one(size, self.regions.base, Spec::splat(false));
@ -270,11 +248,11 @@ impl<'a> GridLayouter<'a> {
// base, for auto it's already correct and for fr we could // base, for auto it's already correct and for fr we could
// only guess anyway. // only guess anyway.
if let TrackSizing::Linear(v) = self.rows[y] { if let TrackSizing::Linear(v) = self.rows[y] {
regions.base.set(self.block, v.resolve(base)); regions.base.h = v.resolve(self.regions.base.h);
} }
let frame = node.layout(ctx, &regions).remove(0).item; let frame = node.layout(ctx, &regions).remove(0).item;
resolved.set_max(frame.size.get(self.inline)); resolved.set_max(frame.size.w);
} }
} }
@ -336,7 +314,7 @@ impl<'a> GridLayouter<'a> {
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y), TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
TrackSizing::Fractional(v) => { TrackSizing::Fractional(v) => {
self.fr += v; self.fr += v;
self.constraints.exact.set(self.block, Some(self.full)); self.cts.exact.y = Some(self.full);
self.lrows.push(Row::Fr(v, y)); self.lrows.push(Row::Fr(v, y));
} }
} }
@ -349,27 +327,22 @@ impl<'a> GridLayouter<'a> {
/// Layout a row with automatic size along the block axis. Such a row may /// Layout a row with automatic size along the block axis. Such a row may
/// break across multiple regions. /// break across multiple regions.
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
let base = self.regions.base.get(self.inline);
let mut resolved: Vec<Length> = vec![]; let mut resolved: Vec<Length> = vec![];
// Determine the size for each region of the row. // Determine the size for each region of the row.
for (x, &rcol) in self.rcols.iter().enumerate() { for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(node) = self.cell(x, y) { if let Some(node) = self.cell(x, y) {
let inline = self.inline;
let mut regions = self.regions.clone(); let mut regions = self.regions.clone();
regions.mutate(|size| size.set(inline, rcol)); regions.mutate(|size| size.w = rcol);
// Set the inline base back to the parent region's inline base // Set the horizontal base back to the parent region's base for
// for auto columns. // auto columns.
if self.cols[x] == TrackSizing::Auto { if self.cols[x] == TrackSizing::Auto {
regions.base.set(self.inline, base); regions.base.w = self.regions.base.w;
} }
let mut sizes = node let mut sizes =
.layout(ctx, &regions) node.layout(ctx, &regions).into_iter().map(|frame| frame.item.size.h);
.into_iter()
.map(|frame| frame.item.size.get(self.block));
for (target, size) in resolved.iter_mut().zip(&mut sizes) { for (target, size) in resolved.iter_mut().zip(&mut sizes) {
target.set_max(size); target.set_max(size);
@ -398,7 +371,7 @@ impl<'a> GridLayouter<'a> {
for (target, (current, _)) in for (target, (current, _)) in
resolved[.. len - 1].iter_mut().zip(self.regions.iter()) resolved[.. len - 1].iter_mut().zip(self.regions.iter())
{ {
target.set_max(current.get(self.block)); target.set_max(current.h);
} }
} }
@ -408,7 +381,7 @@ impl<'a> GridLayouter<'a> {
for (i, frame) in frames.into_iter().enumerate() { for (i, frame) in frames.into_iter().enumerate() {
self.push_row(frame); self.push_row(frame);
if i + 1 < len { if i + 1 < len {
self.constraints.exact.set(self.block, Some(self.full)); self.cts.exact.y = Some(self.full);
self.finish_region(ctx); self.finish_region(ctx);
} }
} }
@ -417,16 +390,13 @@ impl<'a> GridLayouter<'a> {
/// Layout a row with linear sizing along the block axis. Such a row cannot /// 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. /// break across multiple regions, but it may force a region break.
fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
let base = self.regions.base.get(self.block); let resolved = v.resolve(self.regions.base.h);
let resolved = v.resolve(base);
let frame = self.layout_single_row(ctx, resolved, y); let frame = self.layout_single_row(ctx, resolved, y);
// Skip to fitting region. // Skip to fitting region.
let length = frame.size.get(self.block); let length = frame.size.h;
while !self.regions.current.get(self.block).fits(length) while !self.regions.current.h.fits(length) && !self.regions.in_full_last() {
&& !self.regions.in_full_last() self.cts.max.y = Some(self.used.h + length);
{
self.constraints.max.set(self.block, Some(self.used.block + length));
self.finish_region(ctx); self.finish_region(ctx);
// Don't skip multiple regions for gutter and don't push a row. // Don't skip multiple regions for gutter and don't push a row.
@ -442,34 +412,31 @@ impl<'a> GridLayouter<'a> {
fn layout_single_row( fn layout_single_row(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
block: Length, height: Length,
y: usize, y: usize,
) -> Frame { ) -> Frame {
let size = self.complete(block); let mut output = Frame::new(Size::new(self.used.w, height), height);
let mut pos = Point::zero();
let mut output = Frame::new(size, size.h);
let mut pos = Gen::zero();
for (x, &rcol) in self.rcols.iter().enumerate() { for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(node) = self.cell(x, y) { if let Some(node) = self.cell(x, y) {
let size = Gen::new(rcol, block).to_size(self.block); let size = Size::new(rcol, height);
let mut base = self.regions.base;
// Set the base to the size for non-auto rows. // Set the base to the size for non-auto rows.
let sizing = Gen::new(self.cols[x], self.rows[y]).to_spec(self.block); let mut base = self.regions.base;
if sizing.x != TrackSizing::Auto { if self.cols[x] != TrackSizing::Auto {
base.w = size.w; base.w = size.w;
} }
if sizing.y != TrackSizing::Auto { if self.rows[y] != TrackSizing::Auto {
base.h = size.h; base.h = size.h;
} }
let regions = Regions::one(size, base, Spec::splat(true)); let regions = Regions::one(size, base, Spec::splat(true));
let frame = node.layout(ctx, &regions).remove(0); let frame = node.layout(ctx, &regions).remove(0);
output.push_frame(pos.to_point(self.block), frame.item); output.push_frame(pos, frame.item);
} }
pos.inline += rcol; pos.x += rcol;
} }
output output
@ -482,44 +449,41 @@ impl<'a> GridLayouter<'a> {
resolved: &[Length], resolved: &[Length],
y: usize, y: usize,
) -> Vec<Frame> { ) -> Vec<Frame> {
let base = self.regions.base.get(self.inline);
// Prepare frames. // Prepare frames.
let mut outputs: Vec<_> = resolved let mut outputs: Vec<_> = resolved
.iter() .iter()
.map(|&v| self.complete(v)) .map(|&h| Frame::new(Size::new(self.used.w, h), h))
.map(|size| Frame::new(size, size.h))
.collect(); .collect();
// Prepare regions. // Prepare regions.
let size = self.complete(resolved[0]); let size = Size::new(self.used.w, resolved[0]);
let mut regions = Regions::one(size, self.regions.base, Spec::splat(true)); let mut regions = Regions::one(size, self.regions.base, Spec::splat(true));
regions.backlog = resolved[1 ..] regions.backlog = resolved[1 ..]
.iter() .iter()
.map(|&v| self.complete(v)) .map(|&h| Size::new(self.used.w, h))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter(); .into_iter();
// Layout the row. // Layout the row.
let mut pos = Gen::zero(); let mut pos = Point::zero();
for (x, &rcol) in self.rcols.iter().enumerate() { for (x, &rcol) in self.rcols.iter().enumerate() {
if let Some(node) = self.cell(x, y) { if let Some(node) = self.cell(x, y) {
regions.mutate(|size| size.set(self.inline, rcol)); regions.mutate(|size| size.w = rcol);
// Set the inline base back to the parent region's inline base // Set the horizontal base back to the parent region's base for
// for auto columns. // auto columns.
if self.cols[x] == TrackSizing::Auto { if self.cols[x] == TrackSizing::Auto {
regions.base.set(self.inline, base); regions.base.w = self.regions.base.w;
} }
// Push the layouted frames into the individual output frames. // Push the layouted frames into the individual output frames.
let frames = node.layout(ctx, &regions); let frames = node.layout(ctx, &regions);
for (output, frame) in outputs.iter_mut().zip(frames) { for (output, frame) in outputs.iter_mut().zip(frames) {
output.push_frame(pos.to_point(self.block), frame.item); output.push_frame(pos, frame.item);
} }
} }
pos.inline += rcol; pos.x += rcol;
} }
outputs outputs
@ -527,30 +491,28 @@ impl<'a> GridLayouter<'a> {
/// Push a row frame into the current region. /// Push a row frame into the current region.
fn push_row(&mut self, frame: Frame) { fn push_row(&mut self, frame: Frame) {
let length = frame.size.get(self.block); self.regions.current.h -= frame.size.h;
*self.regions.current.get_mut(self.block) -= length; self.used.h += frame.size.h;
self.used.block += length;
self.lrows.push(Row::Frame(frame)); self.lrows.push(Row::Frame(frame));
} }
/// Finish rows for one region. /// Finish rows for one region.
fn finish_region(&mut self, ctx: &mut LayoutContext) { fn finish_region(&mut self, ctx: &mut LayoutContext) {
// Determine the block size of the region. // Determine the height of the region's frame.
let block = if self.fr.is_zero() || self.full.is_infinite() { let height = if self.fr.is_zero() || self.full.is_infinite() {
self.used.block self.used.h
} else { } else {
self.full self.full
}; };
let size = self.complete(block); self.cts.min.y = Some(height);
self.constraints.min.set(self.block, Some(block));
// The frame for the region. // The frame for the region.
let mut output = Frame::new(size, size.h); let mut output = Frame::new(Size::new(self.used.w, height), height);
let mut pos = Gen::zero(); let mut pos = Point::zero();
// Determine the remaining size for fractional rows. // Determine the size that remains for fractional rows.
let remaining = self.full - self.used.block; let remaining = self.full - self.used.h;
// Place finished rows and layout fractional rows. // Place finished rows and layout fractional rows.
for row in std::mem::take(&mut self.lrows) { for row in std::mem::take(&mut self.lrows) {
@ -567,17 +529,17 @@ impl<'a> GridLayouter<'a> {
} }
}; };
let point = pos.to_point(self.block); let height = frame.size.h;
pos.block += frame.size.get(self.block); output.merge_frame(pos, frame);
output.merge_frame(point, frame); pos.y += height;
} }
self.regions.next(); self.regions.next();
self.full = self.regions.current.get(self.block); self.full = self.regions.current.h;
self.used.block = Length::zero(); self.used.h = Length::zero();
self.fr = Fractional::zero(); self.fr = Fractional::zero();
self.finished.push(output.constrain(self.constraints)); self.finished.push(output.constrain(self.cts));
self.constraints = Constraints::new(self.expand); self.cts = Constraints::new(self.expand);
} }
/// Get the node in the cell in column `x` and row `y`. /// Get the node in the cell in column `x` and row `y`.
@ -596,10 +558,4 @@ impl<'a> GridLayouter<'a> {
None None
} }
} }
/// Return a size where the inline axis spans the whole grid and the block
/// axis the given length.
fn complete(&self, block: Length) -> Size {
Gen::new(self.used.inline, block).to_size(self.block)
}
} }

View File

@ -266,49 +266,26 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let columns = args.named("columns")?.unwrap_or_default(); let columns = args.named("columns")?.unwrap_or_default();
let rows = args.named("rows")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default();
let tracks = Gen::new(columns, rows); let tracks = Spec::new(columns, rows);
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default(); let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?; let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?; let row_gutter = args.named("row-gutter")?;
let gutter = Gen::new( let gutter = Spec::new(
column_gutter.unwrap_or_else(|| base_gutter.clone()), column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter), row_gutter.unwrap_or(base_gutter),
); );
let column_dir = args.named("column-dir")?;
let row_dir = args.named("row-dir")?;
let children: Vec<Template> = args.all().collect(); let children: Vec<Template> = args.all().collect();
Ok(Value::Template(Template::from_block(move |style| { Ok(Value::Template(Template::from_block(move |style| {
// If the directions become aligned, try to fix up the direction which
// is not user-defined.
let mut dirs =
Gen::new(column_dir.unwrap_or(style.dir), row_dir.unwrap_or(Dir::TTB));
if dirs.block.axis() == dirs.inline.axis() {
let target = if column_dir.is_some() {
&mut dirs.block
} else {
&mut dirs.inline
};
*target = if target.axis() == style.dir.axis() {
Dir::TTB
} else {
style.dir
};
}
let children =
children.iter().map(|child| child.to_stack(&style).into()).collect();
GridNode { GridNode {
dirs,
tracks: tracks.clone(), tracks: tracks.clone(),
gutter: gutter.clone(), gutter: gutter.clone(),
children, children: children
.iter()
.map(|child| child.to_stack(&style).into())
.collect(),
} }
}))) })))
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -32,19 +32,6 @@
rect(width: 100%, fill: rgb("dddddd"))[aaa], rect(width: 100%, fill: rgb("dddddd"))[aaa],
) )
---
#page(height: 3cm, width: 2cm)
#grid(
columns: (1fr, 1cm, 1fr, 1fr),
column-dir: ttb,
rows: (auto, 1fr),
rect(height: 100%, fill: rgb("222222"))[foo],
rect(height: 100%, fill: rgb("547d0a"))[bar],
rect(height: 100%, fill: eastern)[hab],
rect(height: 100%, fill: conifer)[baz],
rect(height: 100%, width: 100%, fill: rgb("547d0a"))[bar],
)
--- ---
#page(height: 3cm, margins: 0pt) #page(height: 3cm, margins: 0pt)
#align(center) #align(center)