From 4252f959f74b94e0079178b32bf758f889c8cd95 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 6 Oct 2020 18:27:00 +0200 Subject: [PATCH] =?UTF-8?q?Typesafe=20conversions=20in=20stack=20&=20line?= =?UTF-8?q?=20layouters=20=F0=9F=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/geom.rs | 65 +++++++----------- src/layout/line.rs | 72 ++++++++++--------- src/layout/primitive.rs | 128 ++++++++++++++++++---------------- src/layout/stack.rs | 149 +++++++++++++++++----------------------- src/library/align.rs | 4 +- src/library/spacing.rs | 2 +- 6 files changed, 197 insertions(+), 223 deletions(-) diff --git a/src/geom.rs b/src/geom.rs index d69797b0f..27cc87e9e 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -6,9 +6,9 @@ pub use kurbo::*; use std::fmt::{self, Debug, Formatter}; use std::ops::*; -use crate::layout::primitive::{Dir, Gen2, GenAlign, Get, Side, SpecAxis}; +use crate::layout::{Dir, Gen2, GenAlign, Get, Side, Spec2, SpecAxis, Switch}; -macro_rules! impl_get_2d { +macro_rules! impl_2d { ($t:ty, $x:ident, $y:ident) => { impl Get for $t { type Component = f64; @@ -27,12 +27,20 @@ macro_rules! impl_get_2d { } } } + + impl Switch for $t { + type Other = Gen2; + + fn switch(self, dirs: Gen2) -> Self::Other { + Spec2::new(self.$x, self.$y).switch(dirs) + } + } }; } -impl_get_2d!(Point, x, y); -impl_get_2d!(Vec2, x, y); -impl_get_2d!(Size, width, height); +impl_2d!(Point, x, y); +impl_2d!(Vec2, x, y); +impl_2d!(Size, width, height); impl Get for Rect { type Component = f64; @@ -60,61 +68,36 @@ impl Get for Rect { /// /// [sizes]: ../../kurbo/struct.Size.html pub trait SizeExt { - /// Returns the generalized version of a `Size` based on the current - /// directions. - /// - /// In the generalized version: - /// - `x` describes the cross axis instead of the horizontal one. - /// - `y` describes the main axis instead of the vertical one. - fn generalized(self, dirs: Gen2) -> Self; - - /// Returns the specialized version of this generalized `Size` (inverse to - /// `generalized`). - fn specialized(self, dirs: Gen2) -> Self; - /// Whether the given size fits into this one, that is, both coordinate /// values are smaller or equal. fn fits(self, other: Self) -> bool; /// The anchor position for an object to be aligned in a container with this /// size and the given directions. - /// - /// This assumes the size to be generalized such that `width` corresponds to - /// the cross and `height` to the main axis. fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point; } impl SizeExt for Size { - fn generalized(self, dirs: Gen2) -> Self { - match dirs.main.axis() { - SpecAxis::Horizontal => Self::new(self.height, self.width), - SpecAxis::Vertical => self, - } - } - - fn specialized(self, dirs: Gen2) -> Self { - // Even though generalized is its own inverse, we still have this second - // function, for clarity at the call-site. - self.generalized(dirs) - } - fn fits(self, other: Self) -> bool { self.width >= other.width && self.height >= other.height } fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point { fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 { - match (dir.is_positive(), align) { - (true, GenAlign::Start) | (false, GenAlign::End) => 0.0, - (_, GenAlign::Center) => length / 2.0, - (true, GenAlign::End) | (false, GenAlign::Start) => length, + match if dir.is_positive() { align } else { align.inv() } { + GenAlign::Start => 0.0, + GenAlign::Center => length / 2.0, + GenAlign::End => length, } } - Point::new( - anchor(self.width, dirs.cross, aligns.cross), - anchor(self.height, dirs.main, aligns.main), - ) + let switched = self.switch(dirs); + let generic = Gen2::new( + anchor(switched.main, dirs.main, aligns.main), + anchor(switched.cross, dirs.cross, aligns.cross), + ); + + generic.switch(dirs).to_point() } } diff --git a/src/layout/line.rs b/src/layout/line.rs index c1b5920f4..ae3bd9699 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -49,11 +49,12 @@ impl LineLayouter { /// Add a layout. pub fn add(&mut self, layout: BoxLayout, aligns: Gen2) { + let dirs = self.ctx.dirs; if let Some(prev) = self.run.aligns { if aligns.main != prev.main { // TODO: Issue warning for non-fitting alignment in // non-repeating context. - let fitting = self.stack.is_fitting_alignment(aligns); + let fitting = aligns.main >= self.stack.space.allowed_align; if !fitting && self.ctx.repeat { self.finish_space(true); } else { @@ -62,20 +63,20 @@ impl LineLayouter { } else if aligns.cross < prev.cross { self.finish_line(); } else if aligns.cross > prev.cross { - let mut rest_run = LineRun::new(); + let usable = self.stack.usable().get(dirs.cross.axis()); - let usable = self.stack.usable().get(self.ctx.dirs.cross.axis()); + let mut rest_run = LineRun::new(); + rest_run.size.main = self.run.size.main; rest_run.usable = Some(match aligns.cross { GenAlign::Start => unreachable!("start > x"), - GenAlign::Center => usable - 2.0 * self.run.size.width, - GenAlign::End => usable - self.run.size.width, + GenAlign::Center => usable - 2.0 * self.run.size.cross, + GenAlign::End => usable - self.run.size.cross, }); - rest_run.size.height = self.run.size.height; - self.finish_line(); - self.stack.add_spacing(-rest_run.size.height, SpacingKind::Hard); + // Move back up in the stack layouter. + self.stack.add_spacing(-rest_run.size.main, SpacingKind::Hard); self.run = rest_run; } } @@ -84,24 +85,26 @@ impl LineLayouter { self.add_cross_spacing(spacing, SpacingKind::Hard); } - let size = layout.size.generalized(self.ctx.dirs); + let size = layout.size.switch(dirs); + let usable = self.usable(); - if !self.usable().fits(size) { + if usable.main < size.main || usable.cross < size.cross { if !self.line_is_empty() { self.finish_line(); } // TODO: Issue warning about overflow if there is overflow. - if !self.usable().fits(size) { + let usable = self.usable(); + if usable.main < size.main || usable.cross < size.cross { self.stack.skip_to_fitting_space(layout.size); } } self.run.aligns = Some(aligns); - self.run.layouts.push((self.run.size.width, layout)); + self.run.layouts.push((self.run.size.cross, layout)); - self.run.size.width += size.width; - self.run.size.height = self.run.size.height.max(size.height); + self.run.size.cross += size.cross; + self.run.size.main = self.run.size.main.max(size.main); self.run.last_spacing = LastSpacing::None; } @@ -109,16 +112,16 @@ impl LineLayouter { /// /// This specifies how much more would fit before a line break would be /// needed. - fn usable(&self) -> Size { + fn usable(&self) -> Gen2 { // The base is the usable space of the stack layouter. - let mut usable = self.stack.usable().generalized(self.ctx.dirs); + let mut usable = self.stack.usable().switch(self.ctx.dirs); // If there was another run already, override the stack's size. if let Some(cross) = self.run.usable { - usable.width = cross; + usable.cross = cross; } - usable.width -= self.run.size.width; + usable.cross -= self.run.size.cross; usable } @@ -132,8 +135,8 @@ impl LineLayouter { pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { SpacingKind::Hard => { - spacing = spacing.min(self.usable().width); - self.run.size.width += spacing; + spacing = spacing.min(self.usable().cross); + self.run.size.cross += spacing; self.run.last_spacing = LastSpacing::Hard; } @@ -171,13 +174,13 @@ impl LineLayouter { /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> Vec { let mut spaces = self.stack.remaining(); - *spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.height; + *spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.main; spaces } /// Whether the currently set line is empty. pub fn line_is_empty(&self) -> bool { - self.run.size == Size::ZERO && self.run.layouts.is_empty() + self.run.size == Gen2::ZERO && self.run.layouts.is_empty() } /// Finish everything up and return the final collection of boxes. @@ -196,19 +199,20 @@ impl LineLayouter { /// Finish the active line and start a new one. pub fn finish_line(&mut self) { - let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.dirs)); - let aligns = self.run.aligns.unwrap_or_default(); - let cross = self.ctx.dirs.cross; + let dirs = self.ctx.dirs; - let layouts = std::mem::take(&mut self.run.layouts); - for (offset, child) in layouts { - let mut pos = Point::ZERO; - *pos.get_mut(cross.axis()) = if cross.is_positive() { + let mut layout = BoxLayout::new(self.run.size.switch(dirs).to_size()); + let aligns = self.run.aligns.unwrap_or_default(); + + let children = std::mem::take(&mut self.run.layouts); + for (offset, child) in children { + let cross = if dirs.cross.is_positive() { offset } else { - self.run.size.width - offset - child.size.get(cross.axis()) + self.run.size.cross - offset - child.size.get(dirs.cross.axis()) }; + let pos = Gen2::new(0.0, cross).switch(dirs).to_point(); layout.push_layout(pos, child); } @@ -231,15 +235,15 @@ struct LineRun { /// The so-far accumulated items of the run. layouts: Vec<(f64, BoxLayout)>, /// The summed width and maximal height of the run. - size: Size, + size: Gen2, /// The alignment of all layouts in the line. /// /// When a new run is created the alignment is yet to be determined and /// `None` as such. Once a layout is added, its alignment decides the /// alignment for the whole run. aligns: Option>, - /// The amount of space left by another run on the same line or `None` if - /// this is the only run so far. + /// The amount of cross-space left by another run on the same line or `None` + /// if this is the only run so far. usable: Option, /// The spacing state. This influences how new spacing is handled, e.g. hard /// spacing may override soft spacing. @@ -250,7 +254,7 @@ impl LineRun { fn new() -> Self { Self { layouts: vec![], - size: Size::ZERO, + size: Gen2::ZERO, aligns: None, usable: None, last_spacing: LastSpacing::Hard, diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index 549c1f29f..f932b85b5 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -2,6 +2,8 @@ use std::fmt::{self, Display, Formatter}; +use crate::geom::{Point, Size, Vec2}; + /// Generic access to a structure's components. pub trait Get { /// The structure's component type. @@ -14,36 +16,16 @@ pub trait Get { fn get_mut(&mut self, index: Index) -> &mut Self::Component; } -/// Convert a type into its generic representation. +/// Switch between the specific and generic representations of a type. /// /// The generic representation deals with main and cross axes while the specific /// representation deals with horizontal and vertical axes. -/// -/// See also [`ToSpec`] for the inverse conversion. -/// -/// [`ToSpec`]: trait.ToSpec.html -pub trait ToGen { - /// The generic version of this type. - type Output; +pub trait Switch { + /// The type of the other version. + type Other; - /// The generic version of this type based on the current directions. - fn to_gen(self, dirs: Gen2) -> Self::Output; -} - -/// Convert a type into its specific representation. -/// -/// The specific representation deals with horizontal and vertical axes while -/// the generic representation deals with main and cross axes. -/// -/// See also [`ToGen`] for the inverse conversion. -/// -/// [`ToGen`]: trait.ToGen.html -pub trait ToSpec { - /// The specific version of this type. - type Output; - - /// The specific version of this type based on the current directions. - fn to_spec(self, dirs: Gen2) -> Self::Output; + /// The other version of this type based on the current directions. + fn switch(self, dirs: Gen2) -> Self::Other; } /// The four directions into which content can be laid out. @@ -68,6 +50,26 @@ impl Dir { } } + /// The side this direction starts at. + pub fn start(self) -> Side { + match self { + Self::LTR => Side::Left, + Self::RTL => Side::Right, + Self::TTB => Side::Top, + Self::BTT => Side::Bottom, + } + } + + /// The side this direction ends at. + pub fn end(self) -> Side { + match self { + Self::LTR => Side::Right, + Self::RTL => Side::Left, + Self::TTB => Side::Bottom, + Self::BTT => Side::Top, + } + } + /// Whether this direction points into the positive coordinate direction. /// /// The positive directions are left-to-right and top-to-bottom. @@ -86,23 +88,6 @@ impl Dir { if self.is_positive() { 1.0 } else { -1.0 } } - /// The side of this direction the alignment identifies. - /// - /// `Center` alignment is treated the same as `Start` alignment. - pub fn side(self, align: GenAlign) -> Side { - let start = match self { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - }; - - match align { - GenAlign::Start | GenAlign::Center => start, - GenAlign::End => start.inv(), - } - } - /// The inverse direction. pub fn inv(self) -> Self { match self { @@ -159,10 +144,10 @@ impl Get for Gen2 { } } -impl ToSpec for Gen2 { - type Output = Spec2; +impl Switch for Gen2 { + type Other = Spec2; - fn to_spec(self, dirs: Gen2) -> Self::Output { + fn switch(self, dirs: Gen2) -> Self::Other { match dirs.main.axis() { SpecAxis::Horizontal => Spec2::new(self.main, self.cross), SpecAxis::Vertical => Spec2::new(self.cross, self.main), @@ -170,6 +155,11 @@ impl ToSpec for Gen2 { } } +impl Gen2 { + /// The instance that has both components set to zero. + pub const ZERO: Self = Self { main: 0.0, cross: 0.0 }; +} + /// A generic container with two components for the two specific axes. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct Spec2 { @@ -204,10 +194,10 @@ impl Get for Spec2 { } } -impl ToGen for Spec2 { - type Output = Gen2; +impl Switch for Spec2 { + type Other = Gen2; - fn to_gen(self, dirs: Gen2) -> Self::Output { + fn switch(self, dirs: Gen2) -> Self::Other { match dirs.main.axis() { SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical), SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal), @@ -215,6 +205,26 @@ impl ToGen for Spec2 { } } +impl Spec2 { + /// The instance that has both components set to zero. + pub const ZERO: Self = Self { horizontal: 0.0, vertical: 0.0 }; + + /// Convert to a 2D vector. + pub fn to_vec2(self) -> Vec2 { + Vec2::new(self.horizontal, self.vertical) + } + + /// Convert to a point. + pub fn to_point(self) -> Point { + Point::new(self.horizontal, self.vertical) + } + + /// Convert to a size. + pub fn to_size(self) -> Size { + Size::new(self.horizontal, self.vertical) + } +} + /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum GenAxis { @@ -234,10 +244,10 @@ impl GenAxis { } } -impl ToSpec for GenAxis { - type Output = SpecAxis; +impl Switch for GenAxis { + type Other = SpecAxis; - fn to_spec(self, dirs: Gen2) -> Self::Output { + fn switch(self, dirs: Gen2) -> Self::Other { match self { Self::Main => dirs.main.axis(), Self::Cross => dirs.cross.axis(), @@ -273,10 +283,10 @@ impl SpecAxis { } } -impl ToGen for SpecAxis { - type Output = GenAxis; +impl Switch for SpecAxis { + type Other = GenAxis; - fn to_gen(self, dirs: Gen2) -> Self::Output { + fn switch(self, dirs: Gen2) -> Self::Other { if self == dirs.main.axis() { GenAxis::Main } else { @@ -366,11 +376,10 @@ impl SpecAlign { } } -impl ToGen for SpecAlign { - type Output = GenAlign; +impl Switch for SpecAlign { + type Other = GenAlign; - fn to_gen(self, dirs: Gen2) -> Self::Output { - let dirs = dirs.to_spec(dirs); + fn switch(self, dirs: Gen2) -> Self::Other { let get = |dir: Dir, at_positive_start| { if dir.is_positive() == at_positive_start { GenAlign::Start @@ -379,6 +388,7 @@ impl ToGen for SpecAlign { } }; + let dirs = dirs.switch(dirs); match self { Self::Left => get(dirs.horizontal, true), Self::Right => get(dirs.horizontal, false), diff --git a/src/layout/stack.rs b/src/layout/stack.rs index aac72ece8..ab131b75d 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -28,7 +28,7 @@ pub struct StackLayouter { /// The finished layouts. layouts: Vec, /// The in-progress space. - space: Space, + pub(super) space: Space, } /// The context for stack layouting. @@ -57,30 +57,31 @@ impl StackLayouter { /// Add a layout to the stack. pub fn add(&mut self, layout: BoxLayout, aligns: Gen2) { // If the alignment cannot be fitted in this space, finish it. + // // TODO: Issue warning for non-fitting alignment in non-repeating - // context. - if !self.update_rulers(aligns) && self.ctx.repeat { + // context. + if aligns.main < self.space.allowed_align && self.ctx.repeat { self.finish_space(true); } - // Now, we add a possibly cached soft space. If the main alignment - // changed before, a possibly cached space would have already been - // discarded. + // Add a possibly cached soft spacing. if let LastSpacing::Soft(spacing, _) = self.space.last_spacing { self.add_spacing(spacing, SpacingKind::Hard); } - // TODO: Issue warning about overflow if there is overflow. + // TODO: Issue warning about overflow if there is overflow in a + // non-repeating context. if !self.space.usable.fits(layout.size) && self.ctx.repeat { self.skip_to_fitting_space(layout.size); } // Change the usable space and size of the space. - self.update_metrics(layout.size.generalized(self.ctx.dirs)); + self.update_metrics(layout.size.switch(self.ctx.dirs)); // Add the box to the vector and remember that spacings are allowed // again. self.space.layouts.push((layout, aligns)); + self.space.allowed_align = aligns.main; self.space.last_spacing = LastSpacing::None; } @@ -93,10 +94,10 @@ impl StackLayouter { let axis = self.ctx.dirs.main.axis(); spacing = spacing.min(self.space.usable.get(axis)); - let size = Size::new(0.0, spacing); + let size = Gen2::new(spacing, 0.0); self.update_metrics(size); self.space.layouts.push(( - BoxLayout::new(size.specialized(self.ctx.dirs)), + BoxLayout::new(size.switch(self.ctx.dirs).to_size()), Gen2::default(), )); @@ -119,40 +120,18 @@ impl StackLayouter { } } - fn update_metrics(&mut self, added: Size) { - let mut size = self.space.size.generalized(self.ctx.dirs); - let mut extra = self.space.extra.generalized(self.ctx.dirs); + fn update_metrics(&mut self, added: Gen2) { + let mut size = self.space.size.switch(self.ctx.dirs); + let mut extra = self.space.extra.switch(self.ctx.dirs); - size.width += (added.width - extra.width).max(0.0); - size.height += (added.height - extra.height).max(0.0); - extra.width = extra.width.max(added.width); - extra.height = (extra.height - added.height).max(0.0); + size.cross += (added.cross - extra.cross).max(0.0); + size.main += (added.main - extra.main).max(0.0); + extra.cross = extra.cross.max(added.cross); + extra.main = (extra.main - added.main).max(0.0); - self.space.size = size.specialized(self.ctx.dirs); - self.space.extra = extra.specialized(self.ctx.dirs); - *self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.height; - } - - /// Returns true if a space break is necessary. - fn update_rulers(&mut self, aligns: Gen2) -> bool { - let allowed = self.is_fitting_alignment(aligns); - if allowed { - let side = self.ctx.dirs.main.side(GenAlign::Start); - *self.space.rulers.get_mut(side) = aligns.main; - } - allowed - } - - /// Whether a layout with the given alignment can still be layouted into the - /// active space or a space break is necessary. - pub(crate) fn is_fitting_alignment(&self, aligns: Gen2) -> bool { - self.is_fitting_axis(self.ctx.dirs.main, aligns.main) - && self.is_fitting_axis(self.ctx.dirs.cross, aligns.cross) - } - - fn is_fitting_axis(&self, dir: Dir, align: GenAlign) -> bool { - align >= self.space.rulers.get(dir.side(GenAlign::Start)) - && align <= self.space.rulers.get(dir.side(GenAlign::End)).inv() + self.space.size = size.switch(self.ctx.dirs).to_size(); + self.space.extra = extra.switch(self.ctx.dirs).to_size(); + *self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.main; } /// Update the layouting spaces. @@ -202,8 +181,9 @@ impl StackLayouter { /// The remaining usable size. pub fn usable(&self) -> Size { self.space.usable - - Size::new(0.0, self.space.last_spacing.soft_or_zero()) - .specialized(self.ctx.dirs) + - Gen2::new(self.space.last_spacing.soft_or_zero(), 0.0) + .switch(self.ctx.dirs) + .to_size() } /// Whether the current layout space is empty. @@ -227,30 +207,36 @@ impl StackLayouter { /// Finish active current space and start a new one. pub fn finish_space(&mut self, hard: bool) { let dirs = self.ctx.dirs; - let space = self.ctx.spaces[self.space.index]; // ------------------------------------------------------------------ // // Step 1: Determine the full size of the space. // (Mostly done already while collecting the boxes, but here we // expand if necessary.) - let usable = space.usable(); - if space.expansion.horizontal { - self.space.size.width = usable.width; - } - if space.expansion.vertical { - self.space.size.height = usable.height; - } + let space = self.ctx.spaces[self.space.index]; + let start = space.start(); + let padded_size = { + let mut used_size = self.space.size; - let size = self.space.size - space.insets.size(); + let usable = space.usable(); + if space.expansion.horizontal { + used_size.width = usable.width; + } + if space.expansion.vertical { + used_size.height = usable.height; + } + + used_size + }; + + let unpadded_size = padded_size - space.insets.size(); + let mut layout = BoxLayout::new(unpadded_size); // ------------------------------------------------------------------ // // Step 2: Forward pass. Create a bounding box for each layout in which // it will be aligned. Then, go forwards through the boxes and remove // what is taken by previous layouts from the following layouts. - let start = space.start(); - let mut bounds = vec![]; let mut bound = Rect { x0: start.x, @@ -260,16 +246,16 @@ impl StackLayouter { }; for (layout, _) in &self.space.layouts { - // First, we store the bounds calculated so far (which were reduced + // First, store the bounds calculated so far (which were reduced // by the predecessors of this layout) as the initial bounding box // of this layout. bounds.push(bound); - // Then, we reduce the bounding box for the following layouts. This + // Then, reduce the bounding box for the following layouts. This // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at its origin by its // main-axis extent. - *bound.get_mut(dirs.main.side(GenAlign::Start)) += + *bound.get_mut(dirs.main.start()) += dirs.main.factor() * layout.size.get(dirs.main.axis()); } @@ -277,37 +263,30 @@ impl StackLayouter { // Step 3: Backward pass. Reduce the bounding boxes from the previous // layouts by what is taken by the following ones. - // The `x` field stores the maximal cross-axis extent in one - // axis-aligned run, while the `y` fields stores the accumulated - // main-axis extent. let mut main_extent = 0.0; for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() { - // We reduce the bounding box of this layout at its end by the - // accumulated main-axis extent of all layouts we have seen so far - // (which are the layouts after this one since we iterate reversed). - *bound.get_mut(dirs.main.side(GenAlign::End)) -= - dirs.main.factor() * main_extent; + let (layout, _) = child; - // And then, we include this layout's main-axis extent. - main_extent += child.0.size.get(dirs.main.axis()); + // Reduce the bounding box of this layout by the following one's + // main-axis extents. + *bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent; + + // And then, include this layout's main-axis extent. + main_extent += layout.size.get(dirs.main.axis()); } // ------------------------------------------------------------------ // // Step 4: Align each layout in its bounding box and collect everything // into a single finished layout. - let mut layout = BoxLayout::new(size); - let children = std::mem::take(&mut self.space.layouts); for ((child, aligns), bound) in children.into_iter().zip(bounds) { - let size = child.size.specialized(dirs); - - // The space in which this layout is aligned is given by the - // distances between the borders of its bounding box. - let usable = bound.size().generalized(dirs); - let local = usable.anchor(dirs, aligns) - size.anchor(dirs, aligns); - let pos = bound.origin() + local.to_size().specialized(dirs).to_vec2(); + // Align the child in its own bounds. + let local = + bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns); + // Make the local position in the bounds global. + let pos = bound.origin() + local; layout.push_layout(pos, child); } @@ -331,23 +310,21 @@ impl StackLayouter { /// A layout space composed of subspaces which can have different directions and /// alignments. -struct Space { +pub(super) struct Space { /// The index of this space in `ctx.spaces`. index: usize, /// Whether to include a layout for this space even if it would be empty. hard: bool, /// The so-far accumulated layouts. layouts: Vec<(BoxLayout, Gen2)>, - /// The specialized size of this space. + /// The size of this space. size: Size, - /// The specialized remaining space. + /// The remaining space. usable: Size, - /// The specialized extra-needed size to affect the size at all. + /// The extra-needed size to affect the size at all. extra: Size, - /// Dictate which alignments for new boxes are still allowed and which - /// require a new space to be started. For example, after an `End`-aligned - /// item, no `Start`-aligned one can follow. - rulers: Sides, + /// Which alignments for new boxes are still allowed. + pub(super) allowed_align: GenAlign, /// The spacing state. This influences how new spacing is handled, e.g. hard /// spacing may override soft spacing. last_spacing: LastSpacing, @@ -362,7 +339,7 @@ impl Space { size: Size::ZERO, usable, extra: Size::ZERO, - rulers: Sides::uniform(GenAlign::Start), + allowed_align: GenAlign::Start, last_spacing: LastSpacing::Hard, } } diff --git a/src/library/align.rs b/src/library/align.rs index 6c9c675f8..f3280065c 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -54,8 +54,8 @@ fn dedup_aligns( // Check whether we know which axis this alignment belongs to. if let Some(axis) = axis { // We know the axis. - let gen_axis = axis.to_gen(ctx.state.dirs); - let gen_align = align.to_gen(ctx.state.dirs); + let gen_axis = axis.switch(ctx.state.dirs); + let gen_align = align.switch(ctx.state.dirs); if align.axis().map_or(false, |a| a != axis) { ctx.diag(error!( diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 7a55ff98e..e254d5e41 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value { Value::Commands(if let Some(spacing) = spacing { let spacing = spacing.eval(ctx.state.text.font_size()); - let axis = axis.to_gen(ctx.state.dirs); + let axis = axis.switch(ctx.state.dirs); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![]