summaryrefslogtreecommitdiff
path: root/src/layout/stack.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-11 22:06:54 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-11 22:06:54 +0100
commita791ef162868c65284903ab479731e0dc9e7a223 (patch)
tree28384c5647086db87c822b186860492dfce23af3 /src/layout/stack.rs
parentd34707a6ae058560140c83af21365884451e9274 (diff)
Pretty good stack layouter ✈
Diffstat (limited to 'src/layout/stack.rs')
-rw-r--r--src/layout/stack.rs177
1 files changed, 112 insertions, 65 deletions
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 369b1c81..27ca433b 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -1,14 +1,15 @@
use smallvec::smallvec;
-use crate::size::{min, max};
+use crate::size::max;
use super::*;
-/// The stack layouter arranges boxes stacked onto each other.
+/// The stack layouter stack boxes onto each other along the secondary layouting
+/// axis.
///
-/// The boxes are laid out in the direction of the secondary layouting axis and
-/// are aligned along both axes.
+/// The boxes are aligned along both axes according to their requested
+/// alignment.
#[derive(Debug, Clone)]
pub struct StackLayouter {
- /// The context for layouter.
+ /// The context for layouting.
ctx: StackContext,
/// The output layouts.
layouts: MultiLayout,
@@ -17,13 +18,19 @@ pub struct StackLayouter {
}
/// The context for stack layouting.
-///
-/// See [`LayoutContext`] for details about the fields.
#[derive(Debug, Clone)]
pub struct StackContext {
+ /// The spaces to layout in.
pub spaces: LayoutSpaces,
+ /// The initial layouting axes, which can be updated by the
+ /// [`StackLayouter::set_axes`] method.
pub axes: LayoutAxes,
+ /// Which alignment to set on the resulting layout. This affects how it will
+ /// be positioned in a parent box.
pub alignment: LayoutAlignment,
+ /// Whether to output a command which renders a debugging box showing the
+ /// extent of the layout.
+ pub debug: bool,
}
/// A layout space composed of subspaces which can have different axes and
@@ -42,19 +49,26 @@ struct 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),
+ /// Dictates the valid alignments for new boxes in this space.
+ rulers: Rulers,
/// The last added spacing if the last added thing was spacing.
last_spacing: LastSpacing,
}
+/// The rulers of a space dictate which alignments for new boxes are still
+/// allowed and which require a new space to be started.
+#[derive(Debug, Clone)]
+struct Rulers {
+ top: Alignment,
+ bottom: Alignment,
+ left: Alignment,
+ right: Alignment,
+}
+
impl StackLayouter {
/// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter {
- let axes = ctx.axes;
let space = ctx.spaces[0];
-
StackLayouter {
ctx,
layouts: MultiLayout::new(),
@@ -64,23 +78,15 @@ impl StackLayouter {
/// Add a layout to the stack.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
- // 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() {
+ if !self.update_rulers(layout.alignment) {
self.finish_space(true);
}
- if layout.alignment.secondary == *self.secondary_alignment() {
- // Add a cached soft space if there is one and the alignment stayed
- // the same. Soft spaces are discarded if the alignment changes.
- if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
- self.add_spacing(spacing, SpacingKind::Hard);
- }
- } else {
- // 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;
+ // Now, we add a possibly cached soft space. If the secondary alignment
+ // changed before, a possibly cached space would have already been
+ // discarded.
+ if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
+ self.add_spacing(spacing, SpacingKind::Hard);
}
// Find the first space that fits the layout.
@@ -93,8 +99,11 @@ impl StackLayouter {
self.finish_space(true);
}
+ // Change the usable space and size of the space.
self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
+ // Add the box to the vector and remember that spacings are allowed
+ // again.
self.space.layouts.push((self.ctx.axes, layout));
self.space.last_spacing = LastSpacing::None;
@@ -103,7 +112,7 @@ impl StackLayouter {
/// Add multiple layouts to the stack.
///
- /// This function simply calls `add` for each layout.
+ /// This function simply calls `add` repeatedly for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
@@ -121,7 +130,6 @@ impl StackLayouter {
let dimensions = Size2D::with_y(spacing);
self.update_metrics(dimensions);
-
self.space.layouts.push((self.ctx.axes, Layout {
dimensions: dimensions.specialized(self.ctx.axes),
alignment: LayoutAlignment::default(),
@@ -147,6 +155,26 @@ impl StackLayouter {
}
}
+ /// Update the rulers to account for the new layout. Returns true if a
+ /// space break is necessary.
+ fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
+ let axes = self.ctx.axes;
+ let allowed = self.alignment_allowed(axes.primary, alignment.primary)
+ && self.alignment_allowed(axes.secondary, alignment.secondary);
+
+ if allowed {
+ *self.space.rulers.get(axes.secondary) = alignment.secondary;
+ }
+
+ allowed
+ }
+
+ /// Whether the given alignment is still allowed according to the rulers.
+ fn alignment_allowed(&mut self, axis: Axis, alignment: Alignment) -> bool {
+ alignment >= *self.space.rulers.get(axis)
+ && alignment <= self.space.rulers.get(axis.inv()).inv()
+ }
+
/// Update the size metrics to reflect that a layout or spacing with the
/// given generalized dimensions has been added.
fn update_metrics(&mut self, dimensions: Size2D) {
@@ -196,8 +224,12 @@ impl StackLayouter {
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
/// out into these spaces, it will fit into this stack.
pub fn remaining(&self) -> LayoutSpaces {
+ let dimensions = self.space.usable
+ - Size2D::with_y(self.space.last_spacing.soft_or_zero())
+ .specialized(self.ctx.axes);
+
let mut spaces = smallvec![LayoutSpace {
- dimensions: self.space.usable,
+ dimensions,
padding: SizeBox::ZERO,
expand: LayoutExpansion::new(false, false),
}];
@@ -280,18 +312,34 @@ impl StackLayouter {
// Step 3: Backward pass. Reduce the bounding boxes from the previous
// layouts by what is taken by the following ones.
- let mut extent = Size::ZERO;
+ // The `x` field stores the maximal primary extent in one axis-aligned
+ // run, while the `y` fields stores the accumulated secondary extent.
+ let mut extent = Size2D::ZERO;
+ let mut rotated = false;
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
let (axes, layout) = entry;
+ // When the axes get rotated, the the maximal primary size
+ // (`extent.x`) dictates how much secondary extent the whole run
+ // had. This value is thus stored in `extent.y`. The primary extent
+ // is reset for this new axis-aligned run.
+ let is_horizontal = axes.secondary.is_horizontal();
+ if is_horizontal != rotated {
+ extent.y = extent.x;
+ extent.x = Size::ZERO;
+ rotated = is_horizontal;
+ }
+
// We reduce the bounding box of this layout at it's end by the
// accumulated secondary extent of all layouts we have seen so far,
// which are the layouts after this one since we iterate reversed.
- *bound.secondary_end_mut(*axes) -= axes.secondary.factor() * extent;
+ *bound.secondary_end_mut(*axes) -= axes.secondary.factor() * extent.y;
// Then, we add this layout's secondary extent to the accumulator.
- extent += layout.dimensions.secondary(*axes);
+ let size = layout.dimensions.generalized(*axes);
+ extent.x.max_eq(size.x);
+ extent.y += size.y;
}
// ------------------------------------------------------------------ //
@@ -299,38 +347,26 @@ impl StackLayouter {
// into a single finished layout.
let mut actions = LayoutActions::new();
- actions.add(LayoutAction::DebugBox(dimensions));
- let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
+ if self.ctx.debug {
+ actions.add(LayoutAction::DebugBox(dimensions));
+ }
+ let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
- let LayoutAxes { primary, secondary } = axes;
-
let size = layout.dimensions.specialized(axes);
let alignment = layout.alignment;
- // The space in which this layout is aligned is given by it's
- // corresponding bound box.
- let usable = Size2D::new(
- bound.right - bound.left,
- bound.bottom - bound.top
- ).generalized(axes);
-
- let offsets = Size2D {
- x: usable.x.anchor(alignment.primary, primary.is_positive())
- - size.x.anchor(alignment.primary, primary.is_positive()),
- y: usable.y.anchor(alignment.secondary, secondary.is_positive())
- - size.y.anchor(alignment.secondary, secondary.is_positive()),
- };
+ // The space in which this layout is aligned is given by the
+ // distances between the borders of it's bounding box.
+ let usable =
+ Size2D::new(bound.right - bound.left, bound.bottom - bound.top)
+ .generalized(axes);
- let position = Size2D::new(bound.left, bound.top)
- + offsets.specialized(axes);
+ let local = usable.anchor(alignment, axes) - size.anchor(alignment, axes);
+ let pos = Size2D::new(bound.left, bound.top) + local.specialized(axes);
- println!("pos: {}", position);
- println!("usable: {}", usable);
- println!("size: {}", size);
-
- actions.add_layout(position, layout);
+ actions.add_layout(pos, layout);
}
self.layouts.push(Layout {
@@ -339,6 +375,9 @@ impl StackLayouter {
actions: actions.to_vec(),
});
+ // ------------------------------------------------------------------ //
+ // Step 5: Start the next space.
+
self.start_space(self.next_space(), hard);
}
@@ -352,14 +391,6 @@ impl StackLayouter {
fn next_space(&self) -> usize {
(self.space.index + 1).min(self.ctx.spaces.len() - 1)
}
-
- // 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,
- }
- }
}
impl Space {
@@ -371,8 +402,24 @@ impl Space {
size: Size2D::ZERO,
usable,
extra: Size2D::ZERO,
- alignment: (Alignment::Origin, Alignment::Origin),
+ rulers: Rulers {
+ top: Alignment::Origin,
+ bottom: Alignment::Origin,
+ left: Alignment::Origin,
+ right: Alignment::Origin,
+ },
last_spacing: LastSpacing::Hard,
}
}
}
+
+impl Rulers {
+ fn get(&mut self, axis: Axis) -> &mut Alignment {
+ match axis {
+ Axis::TopToBottom => &mut self.top,
+ Axis::BottomToTop => &mut self.bottom,
+ Axis::LeftToRight => &mut self.left,
+ Axis::RightToLeft => &mut self.right,
+ }
+ }
+}