diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-11 22:38:34 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-11 22:38:34 +0200 |
| commit | 607f4395f9fdd0b2d0da6e4c6687c692ede04c7a (patch) | |
| tree | 3d74cd12b21fd1c593f4ac19a386d97a10b4d92c /src/layout | |
| parent | d3bc4ec07349a96c3863ddce63c2e52b5e7e9f2f (diff) | |
Refactor stack layout again 🧣
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/stack.rs | 257 |
1 files changed, 100 insertions, 157 deletions
diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 47dd93ea..6ff287f0 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,206 +1,149 @@ use super::*; /// A node that stacks and aligns its children. -/// -/// # Alignment -/// Individual layouts can be aligned at `Start`, `Center` or `End` along both -/// axes. These alignments are with processed with respect to the size of the -/// finished layout and not the total usable size. This means that a later -/// layout can have influence on the position of an earlier one. Consider the -/// following example. -/// ```typst -/// [align: right][A word.] -/// [align: left][A sentence with a couple more words.] -/// ``` -/// The resulting layout looks like this: -/// ```text -/// |--------------------------------------| -/// | A word. | -/// | | -/// | A sentence with a couple more words. | -/// |--------------------------------------| -/// ``` -/// The position of the first aligned box thus depends on the length of the -/// sentence in the second box. #[derive(Debug, Clone, PartialEq)] pub struct Stack { + /// The `main` and `cross` directions of this stack. + /// + /// The children are stacked along the `main` direction. The `cross` + /// direction is required for aligning the children. pub dirs: Gen<Dir>, - pub children: Vec<LayoutNode>, + /// How to align _this_ stack in _its_ parent. pub aligns: Gen<Align>, - pub expand: Spec<bool>, + /// Whether to expand the axes to fill the area or to fit the content. + pub expansion: Gen<Expansion>, + /// The nodes to be stacked. + pub children: Vec<LayoutNode>, } #[async_trait(?Send)] impl Layout for Stack { - async fn layout( - &self, - ctx: &mut LayoutContext, - constraints: LayoutConstraints, - ) -> Vec<Layouted> { - let mut items = vec![]; - - let size = constraints.spaces[0].size; - let mut space = StackSpace::new(self.dirs, self.expand, size); - let mut i = 0; - + async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { + let mut layouter = StackLayouter::new(self, areas.clone()); for child in &self.children { - let child_constraints = LayoutConstraints { - spaces: { - let mut remaining = vec![LayoutSpace { - base: space.full_size, - size: space.usable, - }]; - let next = (i + 1).min(constraints.spaces.len() - 1); - remaining.extend(&constraints.spaces[next ..]); - remaining - }, - repeat: constraints.repeat, - }; - - for item in child.layout(ctx, child_constraints).await { - match item { - Layouted::Spacing(spacing) => space.push_spacing(spacing), - Layouted::Box(mut boxed, aligns) => { - let mut last = false; - while let Err(back) = space.push_box(boxed, aligns) { - boxed = back; - if last { - break; - } - - items.push(Layouted::Box(space.finish(), self.aligns)); - - if i + 1 < constraints.spaces.len() { - i += 1; - } else { - last = true; - } - - let size = constraints.spaces[i].size; - space = StackSpace::new(self.dirs, self.expand, size); - } - } + for layouted in child.layout(ctx, &layouter.areas).await { + match layouted { + Layouted::Spacing(spacing) => layouter.spacing(spacing), + Layouted::Boxed(boxed, aligns) => layouter.boxed(boxed, aligns), } } } + layouter.finish() + } +} - items.push(Layouted::Box(space.finish(), self.aligns)); - items +impl From<Stack> for LayoutNode { + fn from(stack: Stack) -> Self { + Self::dynamic(stack) } } -struct StackSpace { +struct StackLayouter<'a> { + stack: &'a Stack, + main: SpecAxis, dirs: Gen<Dir>, - expand: Spec<bool>, - boxes: Vec<(BoxLayout, Gen<Align>)>, - full_size: Size, - usable: Size, - used: Size, + areas: Areas, + layouted: Vec<Layouted>, + boxes: Vec<(Length, BoxLayout, Gen<Align>)>, + used: Gen<Length>, ruler: Align, } -impl StackSpace { - fn new(dirs: Gen<Dir>, expand: Spec<bool>, size: Size) -> Self { +impl<'a> StackLayouter<'a> { + fn new(stack: &'a Stack, areas: Areas) -> Self { Self { - dirs, - expand, + stack, + main: stack.dirs.main.axis(), + dirs: stack.dirs, + areas, + layouted: vec![], boxes: vec![], - full_size: size, - usable: size, - used: Size::ZERO, + used: Gen::ZERO, ruler: Align::Start, } } - fn push_box( - &mut self, - boxed: BoxLayout, - aligns: Gen<Align>, - ) -> Result<(), BoxLayout> { - let main = self.dirs.main.axis(); - let cross = self.dirs.cross.axis(); - if aligns.main < self.ruler || !self.usable.fits(boxed.size) { - return Err(boxed); - } - - let size = boxed.size.switch(self.dirs); - *self.used.get_mut(cross) = self.used.get(cross).max(size.cross); - *self.used.get_mut(main) += size.main; - *self.usable.get_mut(main) -= size.main; - self.boxes.push((boxed, aligns)); - self.ruler = aligns.main; - - Ok(()) - } - - fn push_spacing(&mut self, spacing: Length) { - let main = self.dirs.main.axis(); - let max = self.usable.get(main); - let trimmed = spacing.min(max); - *self.used.get_mut(main) += trimmed; - *self.usable.get_mut(main) -= trimmed; - - let size = Gen::new(trimmed, Length::ZERO).switch(self.dirs); - self.boxes.push((BoxLayout::new(size.to_size()), Gen::default())); + fn spacing(&mut self, amount: Length) { + let main_rest = self.areas.current.rem.get_mut(self.main); + let capped = amount.min(*main_rest); + *main_rest -= capped; + self.used.main += capped; } - fn finish(mut self) -> BoxLayout { - let dirs = self.dirs; - let main = dirs.main.axis(); - - if self.expand.horizontal { - self.used.width = self.full_size.width; + fn boxed(&mut self, layout: BoxLayout, aligns: Gen<Align>) { + if self.ruler > aligns.main { + self.finish_area(); } - if self.expand.vertical { - self.used.height = self.full_size.height; + while !self.areas.current.rem.fits(layout.size) { + if self.areas.in_full_last() { + // TODO: Diagnose once the necessary spans exist. + let _ = warning!("cannot fit box into any area"); + break; + } else { + self.finish_area(); + } } - let mut sum = Length::ZERO; - let mut sums = Vec::with_capacity(self.boxes.len() + 1); + let size = layout.size.switch(self.dirs); + self.boxes.push((self.used.main, layout, aligns)); - for (boxed, _) in &self.boxes { - sums.push(sum); - sum += boxed.size.get(main); - } + *self.areas.current.rem.get_mut(self.main) -= size.main; + self.used.main += size.main; + self.used.cross = self.used.cross.max(size.cross); + self.ruler = aligns.main; + } - sums.push(sum); + fn finish_area(&mut self) { + let size = { + let full = self.areas.current.full.switch(self.dirs); + Gen::new( + match self.stack.expansion.main { + Expansion::Fill => full.main, + Expansion::Fit => self.used.main.min(full.main), + }, + match self.stack.expansion.cross { + Expansion::Fill => full.cross, + Expansion::Fit => self.used.cross.min(full.cross), + }, + ) + }; - let mut layout = BoxLayout::new(self.used); - let used = self.used.switch(dirs); + let mut output = BoxLayout::new(size.switch(self.dirs).to_size()); - for (i, (boxed, aligns)) in self.boxes.into_iter().enumerate() { - let size = boxed.size.switch(dirs); + for (before, layout, aligns) in std::mem::take(&mut self.boxes) { + let child_size = layout.size.switch(self.dirs); - let before = sums[i]; - let after = sum - sums[i + 1]; - let main_len = used.main - size.main; - let main_range = if dirs.main.is_positive() { - before .. main_len - after + // Align along the main axis. + let main = aligns.main.apply(if self.dirs.main.is_positive() { + let after_with_self = self.used.main - before; + before .. size.main - after_with_self } else { - main_len - before .. after - }; - - let cross_len = used.cross - size.cross; - let cross_range = if dirs.cross.is_positive() { - Length::ZERO .. cross_len + let before_with_self = before + child_size.main; + let after = self.used.main - (before + child_size.main); + size.main - before_with_self .. after + }); + + // Align along the cross axis. + let cross = aligns.cross.apply(if self.dirs.cross.is_positive() { + Length::ZERO .. size.cross - child_size.cross } else { - cross_len .. Length::ZERO - }; + size.cross - child_size.cross .. Length::ZERO + }); - let main = aligns.main.apply(main_range); - let cross = aligns.cross.apply(cross_range); - let pos = Gen::new(main, cross).switch(dirs).to_point(); - - layout.push_layout(pos, boxed); + let pos = Gen::new(main, cross).switch(self.dirs).to_point(); + output.push_layout(pos, layout); } - layout + self.layouted.push(Layouted::Boxed(output, self.stack.aligns)); + + self.areas.next(); + self.used = Gen::ZERO; + self.ruler = Align::Start; } -} -impl From<Stack> for LayoutNode { - fn from(stack: Stack) -> Self { - Self::dynamic(stack) + fn finish(mut self) -> Vec<Layouted> { + self.finish_area(); + self.layouted } } |
