From 92586d3e6895f0895fb2c88abcdd5e7160482a5b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 10 Dec 2019 11:37:12 +0100 Subject: =?UTF-8?q?Progressing=20stack=20layouter=20=F0=9F=9A=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/stack.rs | 327 +++++++++++++++------------------------------------- 1 file changed, 90 insertions(+), 237 deletions(-) (limited to 'src/layout/stack.rs') diff --git a/src/layout/stack.rs b/src/layout/stack.rs index b11aee79..2e4f2d3a 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,4 +1,5 @@ use smallvec::smallvec; +use crate::size::{min, max}; use super::*; /// The stack layouter arranges boxes stacked onto each other. @@ -13,9 +14,6 @@ pub struct StackLayouter { layouts: MultiLayout, /// The currently active layout space. space: Space, - /// The remaining subspace of the active space. Whenever the layouting axes - /// change a new subspace is started. - sub: Subspace, } /// The context for stack layouting. @@ -37,40 +35,20 @@ struct Space { /// Whether to add the layout for this space even if it would be empty. hard: bool, /// The so-far accumulated subspaces. - subs: Vec, -} - -/// A part of a space with fixed axes and secondary alignment. -#[derive(Debug, Clone)] -struct Subspace { - /// The axes along which contents in this subspace are laid out. - axes: LayoutAxes, - /// The secondary alignment of this subspace. - alignment: Alignment, - /// The beginning of this subspace in the parent space (specialized). - origin: Size2D, - /// The total usable space of this subspace (generalized). - usable: Size2D, - /// The used size of this subspace (generalized), with - /// - `x` being the maximum of the primary size of all boxes. - /// - `y` being the total extent of all boxes and space in the secondary - /// direction. + layouts: Vec<(LayoutAxes, Layout)>, + /// The specialized size of this subspace. size: Size2D, - /// The so-far accumulated layouts. - layouts: Vec, + /// The specialized remaining space. + usable: Size2D, + /// The specialized extra-needed dimensions to affect the size at all. + extra: Size2D, + /// The maximal secondary alignment for both specialized axes (horizontal, + /// vertical). + alignment: (Alignment, Alignment), /// The last added spacing if the last added thing was spacing. last_spacing: LastSpacing, } -/// A single layout in a subspace. -#[derive(Debug, Clone)] -struct LayoutEntry { - /// The offset of this box on the secondary axis. - offset: Size, - /// The layout itself. - layout: Layout, -} - impl StackLayouter { /// Create a new stack layouter. pub fn new(ctx: StackContext) -> StackLayouter { @@ -80,55 +58,55 @@ impl StackLayouter { StackLayouter { ctx, layouts: MultiLayout::new(), - space: Space::new(0, true), - sub: Subspace::new(axes, Alignment::Origin, space.start(), space.usable()), + space: Space::new(0, true, space.usable()), } } /// Add a layout to the stack. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - if layout.alignment.secondary != self.sub.alignment { - self.finish_subspace(layout.alignment.secondary); + // If the layout's secondary alignment is less than what we have already + // seen, it needs to go into the next space. + if layout.alignment.secondary < *self.secondary_alignment() { + self.finish_space(true); } + // We want the new maximal alignment and since the layout's secondary + // alignment is at least the previous maximum, we just take it. + *self.secondary_alignment() = layout.alignment.secondary; + // Add a cached soft space if there is one. - if let LastSpacing::Soft(space, _) = self.sub.last_spacing { - self.add_spacing(space, SpacingKind::Hard); + if let LastSpacing::Soft(spacing, _) = self.space.last_spacing { + self.add_spacing(spacing, SpacingKind::Hard); } - // The new primary size is the maximum of the current one and the - // layout's one while the secondary size grows by the layout's size. - let size = self.ctx.axes.generalize(layout.dimensions); - let mut new_size = Size2D { - x: crate::size::max(self.sub.size.x, size.x), - y: self.sub.size.y + size.y - }; - - // Find the first (sub-)space that fits the layout. - while !self.sub.usable.fits(new_size) { + // Find the first space that fits the layout. + while !self.space.usable.fits(layout.dimensions) { if self.space_is_last() && self.space_is_empty() { - error!("box of size {} does not fit into remaining stack of size {}", - size, self.sub.usable - Size2D::with_y(self.sub.size.y)); + error!("box of size {} does not fit into remaining usable size {}", + layout.dimensions, self.space.usable); } self.finish_space(true); - new_size = size; } - // The secondary offset from the start of layouts is given by the - // current primary size of the subspace. - let offset = self.sub.size.y; - self.sub.layouts.push(LayoutEntry { - offset, - layout, - }); + let axes = self.ctx.axes; + let dimensions = layout.dimensions.generalized(axes); + + let mut size = self.space.size.generalized(axes); + let mut extra = self.space.extra.generalized(axes); + + size.x += max(dimensions.x - extra.x, Size::ZERO); + size.y += max(dimensions.y - extra.y, Size::ZERO); + extra.x = max(extra.x, dimensions.x); + extra.y = max(extra.y - dimensions.y, Size::ZERO); - // The new size of the subspace is the previously calculated - // combination. - self.sub.size = new_size; + self.space.size = size.specialized(axes); + self.space.extra = extra.specialized(axes); - // Since the last item was a box, last spacing is reset to `None`. - self.sub.last_spacing = LastSpacing::None; + *self.space.usable.secondary_mut(axes) -= dimensions.y; + + self.space.layouts.push((self.ctx.axes, layout)); + self.space.last_spacing = LastSpacing::None; Ok(()) } @@ -144,30 +122,34 @@ impl StackLayouter { } /// Add secondary spacing to the stack. - pub fn add_spacing(&mut self, space: Size, kind: SpacingKind) { + pub fn add_spacing(&mut self, mut spacing: Size, kind: SpacingKind) { match kind { // A hard space is directly added to the sub's size. SpacingKind::Hard => { - if self.sub.size.y + space > self.sub.usable.y { - self.sub.size.y = self.sub.usable.y; - } else { - self.sub.size.y += space; - } + // Reduce the spacing such that definitely fits. + spacing.min_eq(self.space.usable.secondary(self.ctx.axes)); - self.sub.last_spacing = LastSpacing::Hard; + self.add(Layout { + dimensions: Size2D::with_y(spacing).specialized(self.ctx.axes), + baseline: None, + alignment: LayoutAlignment::default(), + actions: vec![], + }).expect("spacing should fit"); + + self.space.last_spacing = LastSpacing::Hard; } // A hard space is cached if it is not consumed by a hard space or // previous soft space with higher level. SpacingKind::Soft(level) => { - let consumes = match self.sub.last_spacing { + let consumes = match self.space.last_spacing { LastSpacing::None => true, LastSpacing::Soft(_, prev) if level < prev => true, _ => false, }; if consumes { - self.sub.last_spacing = LastSpacing::Soft(space, level); + self.space.last_spacing = LastSpacing::Soft(spacing, level); } } } @@ -178,13 +160,7 @@ impl StackLayouter { /// This starts a new subspace (if the axes are actually different from the /// current ones). pub fn set_axes(&mut self, axes: LayoutAxes) { - if axes != self.ctx.axes { - self.finish_subspace(Alignment::Origin); - - let (origin, usable) = self.remaining_subspace(); - self.sub = Subspace::new(axes, Alignment::Origin, origin, usable); - self.ctx.axes = axes; - } + self.ctx.axes = axes; } /// Change the layouting spaces to use. @@ -206,9 +182,9 @@ impl StackLayouter { /// out into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = smallvec![LayoutSpace { - dimensions: self.remaining_subspace().1, - padding: SizeBox::zero(), - expand: (false, false), + dimensions: self.space.usable, + padding: SizeBox::ZERO, + expand: LayoutExpansion::new(false, false), }]; for space in &self.ctx.spaces[self.next_space()..] { @@ -220,12 +196,12 @@ impl StackLayouter { /// The usable size along the primary axis. pub fn primary_usable(&self) -> Size { - self.sub.usable.x + self.space.usable.primary(self.ctx.axes) } /// Whether the current layout space (not subspace) is empty. pub fn space_is_empty(&self) -> bool { - self.subspace_is_empty() && self.space.subs.is_empty() + self.space.size == Size2D::ZERO && self.space.layouts.is_empty() } /// Whether the current layout space is the last is the followup list. @@ -243,117 +219,36 @@ impl StackLayouter { /// Finish the current space and start a new one. pub fn finish_space(&mut self, hard: bool) { - self.finish_subspace(Alignment::Origin); - - println!(); - println!("FINISHING SPACE:"); - println!(); - let space = self.ctx.spaces[self.space.index]; - let mut subs = std::mem::replace(&mut self.space.subs, vec![]); - // --------------------------------------------------------------------- - // Compute the size of the whole space. let usable = space.usable(); - let mut max = Size2D { - x: if space.expand.0 { usable.x } else { Size::zero() }, - y: if space.expand.1 { usable.y } else { Size::zero() }, - }; - - // The total size is determined by the maximum position + extent of one - // of the boxes. - for sub in &subs { - max.max_eq(sub.origin + sub.axes.specialize(sub.size)); - } + if space.expand.horizontal { self.space.size.x = usable.x; } + if space.expand.vertical { self.space.size.y = usable.y; } - let dimensions = max.padded(space.padding); - - println!("WITH DIMENSIONS: {}", dimensions); - - println!("SUBS: {:#?}", subs); - - // --------------------------------------------------------------------- - // Justify the boxes according to their alignment and give each box - // the appropriate origin and usable space. - - // use Alignment::*; - - for sub in &mut subs { - // The usable width should not exceed the total usable width - // (previous value) or the maximum width of the layout as a whole. - sub.usable.x = crate::size::min( - sub.usable.x, - sub.axes.specialize(max - sub.origin).x, - ); - - sub.usable.y = sub.size.y; - } + let dimensions = self.space.size.padded(space.padding); - // if space.expand.1 { - // let height = subs.iter().map(|sub| sub.size.y).sum(); - // let centers = subs.iter() - // .filter(|sub| sub.alignment == Alignment::Center) - // .count() - // .max(1); - - // let grow = max.y - height; - // let center_grow = grow / (centers as i32); - - // println!("center grow = {}", center_grow); - - // let mut offset = Size::zero(); - // for sub in &mut subs { - // sub.origin.y += offset; - // if sub.alignment == Center { - // sub.usable.y += center_grow; - // offset += center_grow; - // } - // } - - // if let Some(last) = subs.last_mut() { - // last.usable.y += grow - offset; - // } - // } - - // --------------------------------------------------------------------- - // Do the thing - - // Add a debug box with this boxes size. let mut actions = LayoutActions::new(); actions.add(LayoutAction::DebugBox(dimensions)); - for sub in subs { - let LayoutAxes { primary, secondary } = sub.axes; - - // The factor is +1 if the axis is positive and -1 otherwise. - let factor = sub.axes.secondary.factor(); - - // The anchor is the position of the origin-most point of the - // layout. - let anchor = - sub.usable.y.anchor(sub.alignment, secondary.is_positive()) - - factor * sub.size.y.anchor(sub.alignment, true); + let mut cursor = space.start(); + for (axes, layout) in std::mem::replace(&mut self.space.layouts, vec![]) { + let LayoutAxes { primary, secondary } = axes; + let size = layout.dimensions.specialized(axes); + let alignment = layout.alignment.primary; - for entry in sub.layouts { - let layout = entry.layout; - let alignment = layout.alignment.primary; - let size = sub.axes.generalize(layout.dimensions); + let primary_usable = self.space.size.primary(axes) - cursor.primary(axes); - let x = - sub.usable.x.anchor(alignment, primary.is_positive()) - - size.x.anchor(alignment, primary.is_positive()); + let position = Size2D { + x: cursor.primary(axes) + + primary_usable.anchor(alignment, primary.is_positive()) + - size.x.anchor(alignment, primary.is_positive()), + y: cursor.secondary(axes), + }; - let y = anchor - + factor * entry.offset - - size.y.anchor(Alignment::Origin, secondary.is_positive()); - - let pos = sub.origin + sub.axes.specialize(Size2D::new(x, y)); - actions.add_layout(pos, layout); - } + actions.add_layout(position.specialized(axes), layout); + *cursor.secondary_mut(axes) += size.y; } - // --------------------------------------------------------------------- - self.layouts.push(Layout { dimensions, baseline: None, @@ -365,14 +260,9 @@ impl StackLayouter { } /// Start a new space with the given index. - fn start_space(&mut self, space: usize, hard: bool) { - // Start the space. - self.space = Space::new(space, hard); - - // Start the subspace. - let space = self.ctx.spaces[space]; - let axes = self.ctx.axes; - self.sub = Subspace::new(axes, Alignment::Origin, space.start(), space.usable()); + fn start_space(&mut self, index: usize, hard: bool) { + let space = self.ctx.spaces[index]; + self.space = Space::new(index, hard, space.usable()); } /// The index of the next space. @@ -380,62 +270,25 @@ impl StackLayouter { (self.space.index + 1).min(self.ctx.spaces.len() - 1) } - /// Finish the current subspace. - fn finish_subspace(&mut self, new_alignment: Alignment) { - let empty = self.subspace_is_empty(); - - let axes = self.ctx.axes; - let (origin, usable) = self.remaining_subspace(); - let new_sub = Subspace::new(axes, new_alignment, origin, usable); - let sub = std::mem::replace(&mut self.sub, new_sub); - - if !empty { - self.space.subs.push(sub); + // Access the secondary alignment in the current system of axes. + fn secondary_alignment(&mut self) -> &mut Alignment { + match self.ctx.axes.primary.is_horizontal() { + true => &mut self.space.alignment.1, + false => &mut self.space.alignment.0, } } - - /// The remaining sub - fn remaining_subspace(&self) -> (Size2D, Size2D) { - let offset = self.sub.size.y + self.sub.last_spacing.soft_or_zero(); - - let new_origin = self.sub.origin + match self.ctx.axes.secondary.is_positive() { - true => self.ctx.axes.specialize(Size2D::with_y(offset)), - false => Size2D::zero(), - }; - - let new_usable = self.ctx.axes.specialize(Size2D { - x: self.sub.usable.x, - y: self.sub.usable.y - offset, - }); - - (new_origin, new_usable) - } - - /// Whether the current layout space (not subspace) is empty. - fn subspace_is_empty(&self) -> bool { - self.sub.layouts.is_empty() && self.sub.size == Size2D::zero() - } } impl Space { - fn new(index: usize, hard: bool) -> Space { + fn new(index: usize, hard: bool, usable: Size2D) -> Space { Space { index, hard, - subs: vec![], - } - } -} - -impl Subspace { - fn new(axes: LayoutAxes, alignment: Alignment, origin: Size2D, usable: Size2D) -> Subspace { - Subspace { - axes, - alignment, - origin, - usable: axes.generalize(usable), - size: Size2D::zero(), layouts: vec![], + size: Size2D::ZERO, + usable, + extra: Size2D::ZERO, + alignment: (Alignment::Origin, Alignment::Origin), last_spacing: LastSpacing::Hard, } } -- cgit v1.2.3