summaryrefslogtreecommitdiff
path: root/src/library/grid.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-28 15:50:48 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-28 23:54:34 +0100
commit3ca5b238238e1128aa7bbfbd5db9e632045d8600 (patch)
tree2471f4b340a15695b7f4d518c0b39fabaea676c4 /src/library/grid.rs
parentb63c21c91d99a1554a019dc275f955d3e6a34271 (diff)
Reorganize library
Diffstat (limited to 'src/library/grid.rs')
-rw-r--r--src/library/grid.rs581
1 files changed, 0 insertions, 581 deletions
diff --git a/src/library/grid.rs b/src/library/grid.rs
deleted file mode 100644
index 2d8cb462..00000000
--- a/src/library/grid.rs
+++ /dev/null
@@ -1,581 +0,0 @@
-//! Layout along a row and column raster.
-
-use super::prelude::*;
-
-/// Arrange nodes in a grid.
-#[derive(Debug, Hash)]
-pub struct GridNode {
- /// Defines sizing for content rows and columns.
- pub tracks: Spec<Vec<TrackSizing>>,
- /// Defines sizing of gutter rows and columns between content.
- pub gutter: Spec<Vec<TrackSizing>>,
- /// The nodes to be arranged in a grid.
- pub children: Vec<LayoutNode>,
-}
-
-#[class]
-impl GridNode {
- fn construct(_: &mut Context, args: &mut Args) -> TypResult<Template> {
- let columns = args.named("columns")?.unwrap_or_default();
- let rows = args.named("rows")?.unwrap_or_default();
- let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
- let column_gutter = args.named("column-gutter")?;
- let row_gutter = args.named("row-gutter")?;
- Ok(Template::block(Self {
- tracks: Spec::new(columns, rows),
- gutter: Spec::new(
- column_gutter.unwrap_or_else(|| base_gutter.clone()),
- row_gutter.unwrap_or(base_gutter),
- ),
- children: args.all()?,
- }))
- }
-}
-
-impl Layout for GridNode {
- fn layout(
- &self,
- ctx: &mut Context,
- regions: &Regions,
- styles: StyleChain,
- ) -> TypResult<Vec<Arc<Frame>>> {
- // Prepare grid layout by unifying content and gutter tracks.
- let layouter = GridLayouter::new(
- self.tracks.as_deref(),
- self.gutter.as_deref(),
- &self.children,
- regions,
- styles,
- );
-
- // Measure the columns and layout the grid row-by-row.
- layouter.layout(ctx)
- }
-}
-
-/// Defines how to size a grid cell along an axis.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum TrackSizing {
- /// Fit the cell to its contents.
- Auto,
- /// A length stated in absolute values and/or relative to the parent's size.
- Linear(Linear),
- /// A length that is the fraction of the remaining free space in the parent.
- Fractional(Fractional),
-}
-
-castable! {
- Vec<TrackSizing>,
- Expected: "integer or (auto, linear, fractional, or array thereof)",
- Value::Auto => vec![TrackSizing::Auto],
- Value::Length(v) => vec![TrackSizing::Linear(v.into())],
- Value::Relative(v) => vec![TrackSizing::Linear(v.into())],
- Value::Linear(v) => vec![TrackSizing::Linear(v)],
- Value::Fractional(v) => vec![TrackSizing::Fractional(v)],
- Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()],
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .collect(),
-}
-
-castable! {
- TrackSizing,
- Expected: "auto, linear, or fractional",
- Value::Auto => Self::Auto,
- Value::Length(v) => Self::Linear(v.into()),
- Value::Relative(v) => Self::Linear(v.into()),
- Value::Linear(v) => Self::Linear(v),
- Value::Fractional(v) => Self::Fractional(v),
-}
-
-/// Performs grid layout.
-pub struct GridLayouter<'a> {
- /// The grid cells.
- cells: &'a [LayoutNode],
- /// The column tracks including gutter tracks.
- cols: Vec<TrackSizing>,
- /// The row tracks including gutter tracks.
- rows: Vec<TrackSizing>,
- /// The regions to layout children into.
- regions: Regions,
- /// The inherited styles.
- styles: StyleChain<'a>,
- /// Resolved column sizes.
- rcols: Vec<Length>,
- /// Rows in the current region.
- lrows: Vec<Row>,
- /// The full height of the current region.
- full: Length,
- /// The used-up size of the current region. The horizontal size is
- /// determined once after columns are resolved and not touched again.
- used: Size,
- /// The sum of fractional ratios in the current region.
- fr: Fractional,
- /// Frames for finished regions.
- finished: Vec<Arc<Frame>>,
-}
-
-/// Produced by initial row layout, auto and linear rows are already finished,
-/// fractional rows not yet.
-enum Row {
- /// Finished row frame of auto or linear row.
- Frame(Frame),
- /// Ratio of a fractional row and y index of the track.
- Fr(Fractional, usize),
-}
-
-impl<'a> GridLayouter<'a> {
- /// 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 [LayoutNode],
- regions: &Regions,
- styles: StyleChain<'a>,
- ) -> Self {
- let mut cols = vec![];
- let mut rows = vec![];
-
- // Number of content columns: Always at least one.
- 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 = cells.len();
- let given = tracks.y.len();
- let needed = len / c + (len % c).clamp(0, 1);
- given.max(needed)
- };
-
- let auto = TrackSizing::Auto;
- let zero = TrackSizing::Linear(Linear::zero());
- let get_or = |tracks: &[_], idx, default| {
- tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
- };
-
- // Collect content and gutter columns.
- for x in 0 .. c {
- 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(tracks.y, y, auto));
- rows.push(get_or(gutter.y, y, zero));
- }
-
- // Remove superfluous gutter tracks.
- cols.pop();
- rows.pop();
-
- let full = regions.first.y;
- let rcols = vec![Length::zero(); cols.len()];
- let lrows = vec![];
-
- // 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 {
- cells,
- cols,
- rows,
- regions,
- styles,
- rcols,
- lrows,
- full,
- used: Size::zero(),
- fr: Fractional::zero(),
- finished: vec![],
- }
- }
-
- /// Determines the columns sizes and then layouts the grid row-by-row.
- pub fn layout(mut self, ctx: &mut Context) -> TypResult<Vec<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.lrows.push(Row::Fr(v, y));
- self.fr += v;
- }
- }
- }
-
- self.finish_region(ctx)?;
- Ok(self.finished)
- }
-
- /// Determine all column sizes.
- fn measure_columns(&mut self, ctx: &mut Context) -> TypResult<()> {
- // Sum of sizes of resolved linear tracks.
- let mut linear = Length::zero();
-
- // Sum of fractions of all fractional tracks.
- let mut fr = Fractional::zero();
-
- // Resolve the size of all linear columns and compute the sum of all
- // fractional tracks.
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- match col {
- TrackSizing::Auto => {}
- TrackSizing::Linear(v) => {
- let resolved = v.resolve(self.regions.base.x);
- *rcol = resolved;
- linear += resolved;
- }
- TrackSizing::Fractional(v) => fr += v,
- }
- }
-
- // Size that is not used by fixed-size columns.
- let available = self.regions.first.x - linear;
- if available >= Length::zero() {
- // Determine size of auto columns.
- let (auto, count) = self.measure_auto_columns(ctx, available)?;
-
- // If there is remaining space, distribute it to fractional columns,
- // otherwise shrink auto columns.
- let remaining = available - auto;
- if remaining >= Length::zero() {
- if !fr.is_zero() {
- self.grow_fractional_columns(remaining, fr);
- }
- } else {
- self.shrink_auto_columns(available, count);
- }
- }
-
- // Sum up the resolved column sizes once here.
- self.used.x = self.rcols.iter().sum();
-
- Ok(())
- }
-
- /// Measure the size that is available to auto columns.
- fn measure_auto_columns(
- &mut self,
- ctx: &mut Context,
- available: Length,
- ) -> TypResult<(Length, usize)> {
- let mut auto = Length::zero();
- let mut count = 0;
-
- // Determine size of auto columns by laying out all cells in those
- // columns, measuring them and finding the largest one.
- for (x, &col) in self.cols.iter().enumerate() {
- if col != TrackSizing::Auto {
- continue;
- }
-
- let mut resolved = Length::zero();
- for y in 0 .. self.rows.len() {
- if let Some(node) = self.cell(x, y) {
- let size = Size::new(available, self.regions.base.y);
- let mut pod =
- Regions::one(size, self.regions.base, Spec::splat(false));
-
- // For linear rows, we can already resolve the correct
- // base, for auto it's already correct and for fr we could
- // only guess anyway.
- if let TrackSizing::Linear(v) = self.rows[y] {
- pod.base.y = v.resolve(self.regions.base.y);
- }
-
- let frame = node.layout(ctx, &pod, self.styles)?.remove(0);
- resolved.set_max(frame.size.x);
- }
- }
-
- self.rcols[x] = resolved;
- auto += resolved;
- count += 1;
- }
-
- Ok((auto, count))
- }
-
- /// Distribute remaining space to fractional columns.
- fn grow_fractional_columns(&mut self, remaining: Length, fr: Fractional) {
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if let TrackSizing::Fractional(v) = col {
- *rcol = v.resolve(fr, remaining);
- }
- }
- }
-
- /// Redistribute space to auto columns so that each gets a fair share.
- fn shrink_auto_columns(&mut self, available: Length, count: usize) {
- // The fair share each auto column may have.
- let fair = available / count as f64;
-
- // The number of overlarge auto columns and the space that will be
- // equally redistributed to them.
- let mut overlarge: usize = 0;
- let mut redistribute = available;
-
- // Find out the number of and space used by overlarge auto columns.
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if col == TrackSizing::Auto {
- if *rcol > fair {
- overlarge += 1;
- } else {
- redistribute -= *rcol;
- }
- }
- }
-
- // Redistribute the space equally.
- let share = redistribute / overlarge as f64;
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if col == TrackSizing::Auto && *rcol > fair {
- *rcol = share;
- }
- }
- }
-
- /// Layout a row with automatic height. Such a row may break across multiple
- /// regions.
- fn layout_auto_row(&mut self, ctx: &mut Context, y: usize) -> TypResult<()> {
- let mut resolved: Vec<Length> = vec![];
-
- // Determine the size for each region of the row.
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
- let mut pod = self.regions.clone();
- pod.first.x = rcol;
-
- // All widths should be `rcol` except the base for auto columns.
- if self.cols[x] == TrackSizing::Auto {
- pod.base.x = self.regions.base.x;
- }
-
- let mut sizes = node
- .layout(ctx, &pod, self.styles)?
- .into_iter()
- .map(|frame| frame.size.y);
-
- // For each region, we want to know the maximum height any
- // column requires.
- for (target, size) in resolved.iter_mut().zip(&mut sizes) {
- target.set_max(size);
- }
-
- // New heights are maximal by virtue of being new. Note that
- // this extend only uses the rest of the sizes iterator.
- resolved.extend(sizes);
- }
- }
-
- // Nothing to layout.
- if resolved.is_empty() {
- return Ok(());
- }
-
- // Layout into a single region.
- if let &[first] = resolved.as_slice() {
- let frame = self.layout_single_row(ctx, first, y)?;
- self.push_row(frame);
- return Ok(());
- }
-
- // Expand all but the last region if the space is not
- // eaten up by any fr rows.
- if self.fr.is_zero() {
- let len = resolved.len();
- for (region, target) in self.regions.iter().zip(&mut resolved[.. len - 1]) {
- target.set_max(region.y);
- }
- }
-
- // Layout into multiple regions.
- let frames = self.layout_multi_row(ctx, &resolved, y)?;
- let len = frames.len();
- for (i, frame) in frames.into_iter().enumerate() {
- self.push_row(frame);
- if i + 1 < len {
- self.finish_region(ctx)?;
- }
- }
-
- Ok(())
- }
-
- /// Layout a row with linear height. Such a row cannot break across multiple
- /// regions, but it may force a region break.
- fn layout_linear_row(
- &mut self,
- ctx: &mut Context,
- v: Linear,
- y: usize,
- ) -> TypResult<()> {
- let resolved = v.resolve(self.regions.base.y);
- let frame = self.layout_single_row(ctx, resolved, y)?;
-
- // Skip to fitting region.
- let height = frame.size.y;
- while !self.regions.first.y.fits(height) && !self.regions.in_last() {
- self.finish_region(ctx)?;
-
- // Don't skip multiple regions for gutter and don't push a row.
- if y % 2 == 1 {
- return Ok(());
- }
- }
-
- self.push_row(frame);
-
- Ok(())
- }
-
- /// Layout a row with fixed height and return its frame.
- fn layout_single_row(
- &self,
- ctx: &mut Context,
- height: Length,
- y: usize,
- ) -> TypResult<Frame> {
- let mut output = Frame::new(Size::new(self.used.x, height));
- let mut pos = Point::zero();
-
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
- let size = Size::new(rcol, height);
-
- // Set the base to the region's base for auto rows and to the
- // size for linear and fractional rows.
- let base = Spec::new(self.cols[x], self.rows[y])
- .map(|s| s == TrackSizing::Auto)
- .select(self.regions.base, size);
-
- let pod = Regions::one(size, base, Spec::splat(true));
- let frame = node.layout(ctx, &pod, self.styles)?.remove(0);
- output.push_frame(pos, frame);
- }
-
- pos.x += rcol;
- }
-
- Ok(output)
- }
-
- /// Layout a row spanning multiple regions.
- fn layout_multi_row(
- &self,
- ctx: &mut Context,
- heights: &[Length],
- y: usize,
- ) -> TypResult<Vec<Frame>> {
- // Prepare frames.
- let mut outputs: Vec<_> = heights
- .iter()
- .map(|&h| Frame::new(Size::new(self.used.x, h)))
- .collect();
-
- // Prepare regions.
- let size = Size::new(self.used.x, heights[0]);
- let mut pod = Regions::one(size, self.regions.base, Spec::splat(true));
- pod.backlog = heights[1 ..].to_vec();
-
- // Layout the row.
- let mut pos = Point::zero();
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(node) = self.cell(x, y) {
- pod.first.x = rcol;
-
- // All widths should be `rcol` except the base for auto columns.
- if self.cols[x] == TrackSizing::Auto {
- pod.base.x = self.regions.base.x;
- }
-
- // Push the layouted frames into the individual output frames.
- let frames = node.layout(ctx, &pod, self.styles)?;
- for (output, frame) in outputs.iter_mut().zip(frames) {
- output.push_frame(pos, frame);
- }
- }
-
- pos.x += rcol;
- }
-
- Ok(outputs)
- }
-
- /// Push a row frame into the current region.
- fn push_row(&mut self, frame: Frame) {
- self.regions.first.y -= frame.size.y;
- self.used.y += frame.size.y;
- self.lrows.push(Row::Frame(frame));
- }
-
- /// Finish rows for one region.
- fn finish_region(&mut self, ctx: &mut Context) -> TypResult<()> {
- // Determine the size of the grid in this region, expanding fully if
- // there are fr rows.
- let mut size = self.used;
- if self.fr.get() > 0.0 && self.full.is_finite() {
- size.y = self.full;
- }
-
- // The frame for the region.
- let mut output = Frame::new(size);
- let mut pos = Point::zero();
-
- // Place finished rows and layout fractional rows.
- for row in std::mem::take(&mut self.lrows) {
- let frame = match row {
- Row::Frame(frame) => frame,
- Row::Fr(v, y) => {
- let remaining = self.full - self.used.y;
- let height = v.resolve(self.fr, remaining);
- self.layout_single_row(ctx, height, y)?
- }
- };
-
- let height = frame.size.y;
- output.merge_frame(pos, frame);
- pos.y += height;
- }
-
- self.finished.push(Arc::new(output));
- self.regions.next();
- self.full = self.regions.first.y;
- self.used.y = Length::zero();
- self.fr = Fractional::zero();
-
- Ok(())
- }
-
- /// Get the node in the cell in column `x` and row `y`.
- ///
- /// Returns `None` if it's a gutter cell.
- #[track_caller]
- fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> {
- assert!(x < self.cols.len());
- assert!(y < self.rows.len());
-
- // 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.cells.get((y / 2) * c + x / 2)
- } else {
- None
- }
- }
-}