summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-18 19:36:50 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-18 19:36:50 +0100
commitf8233a4cb149c1a69ca3ecc954d45edcd064851b (patch)
treeeb19049cccc76b5bad58a6f0c88d8e9c0a180881 /src
parent1a6fb48bc5e95d0a9ef243ab62517557189c0eea (diff)
Refactor stack layouter to layout eagerly 🛫
Diffstat (limited to 'src')
-rw-r--r--src/layout/flex.rs8
-rw-r--r--src/layout/mod.rs8
-rw-r--r--src/layout/stacked.rs233
-rw-r--r--src/layout/tree.rs2
4 files changed, 116 insertions, 135 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 142530a6..e61e45df 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -76,7 +76,7 @@ impl FlexLayouter {
shrink_to_fit: ctx.shrink_to_fit,
});
- let usable = stack.usable().x;
+ let usable = stack.primary_usable();
FlexLayouter {
axes: ctx.axes,
flex_spacing: ctx.flex_spacing,
@@ -134,7 +134,7 @@ impl FlexLayouter {
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.box_is_empty() && self.stack.space_is_empty() {
self.stack.set_spaces(spaces, true);
- self.total_usable = self.stack.usable().x;
+ self.total_usable = self.stack.primary_usable();
self.usable = self.total_usable;
self.space = None;
} else {
@@ -147,7 +147,7 @@ impl FlexLayouter {
/// The layouter is not consumed by this to prevent ownership problems
/// with borrowed layouters. The state of the layouter is not reset.
/// Therefore, it should not be further used after calling `finish`.
- pub fn finish(&mut self) -> LayoutResult<MultiLayout> {
+ pub fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_box()?;
Ok(self.stack.finish())
}
@@ -204,7 +204,7 @@ impl FlexLayouter {
}
self.stack.finish_layout(true);
- self.total_usable = self.stack.usable().x;
+ self.total_usable = self.stack.primary_usable();
self.usable = self.total_usable;
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 426a51ec..13b63568 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -275,6 +275,14 @@ impl Axis {
Axis::RightToLeft | Axis::BottomToTop => false,
}
}
+
+ /// The direction factor for this axis.
+ ///
+ /// - 1 if the axis is positive.
+ /// - -1 if the axis is negative.
+ pub fn factor(&self) -> i32 {
+ if self.is_positive() { 1 } else { -1 }
+ }
}
/// Where to align content.
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index a5db5638..4acbecf3 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -1,23 +1,18 @@
use smallvec::smallvec;
use super::*;
-/// Layouts boxes stack-like.
-///
-/// The boxes are arranged along an axis, each layout gettings it's own "line".
#[derive(Debug, Clone)]
pub struct StackLayouter {
ctx: StackContext,
layouts: MultiLayout,
- merged_actions: LayoutActionList,
- merged_dimensions: Size2D,
-
- // Offset on secondary axis, anchor of the layout and the layout itself.
- boxes: Vec<(Size, Size2D, Layout)>,
- usable: Size2D,
- dimensions: Size2D,
- active_space: usize,
+ space: usize,
hard: bool,
+ start: Size2D,
+ actions: LayoutActionList,
+ combined_dimensions: Size2D,
+
+ sub: Subspace,
}
/// The context for stack layouting.
@@ -30,52 +25,74 @@ pub struct StackContext {
pub shrink_to_fit: bool,
}
+#[derive(Debug, Clone)]
+struct Subspace {
+ usable: Size2D,
+ anchor: Size2D,
+ factor: i32,
+ dimensions: Size2D,
+}
+
+impl Subspace {
+ fn new(usable: Size2D, axes: LayoutAxes) -> Subspace {
+ Subspace {
+ usable,
+ anchor: axes.anchor(usable),
+ factor: axes.secondary.axis.factor(),
+ dimensions: Size2D::zero(),
+ }
+ }
+}
+
impl StackLayouter {
/// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter {
let space = ctx.spaces[0];
let usable = ctx.axes.generalize(space.usable());
+ let axes = ctx.axes;
StackLayouter {
ctx,
layouts: MultiLayout::new(),
- merged_actions: LayoutActionList::new(),
- merged_dimensions: Size2D::zero(),
-
- boxes: vec![],
- usable,
- active_space: 0,
- dimensions: Size2D::zero(),
+ space: 0,
hard: true,
+ start: space.start(),
+ actions: LayoutActionList::new(),
+ combined_dimensions: Size2D::zero(),
+
+ sub: Subspace::new(usable, axes),
}
}
- /// Add a sublayout.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
let size = self.ctx.axes.generalize(layout.dimensions);
- let mut new_dimensions = merge_sizes(self.dimensions, size);
+ let mut new_dimensions = merge_sizes(self.sub.dimensions, size);
- // Search for a suitable space to insert the box.
- while !self.usable.fits(new_dimensions) {
- if self.boxes.is_empty() && self.in_last_space() {
+ while !self.sub.usable.fits(new_dimensions) {
+ if self.space_is_empty() {
Err(LayoutError::NotEnoughSpace("cannot fit box into stack"))?;
}
self.finish_layout(true);
- new_dimensions = merge_sizes(self.dimensions, size);
+ new_dimensions = merge_sizes(self.sub.dimensions, size);
}
- let offset = self.dimensions.y;
+ let offset = self.sub.dimensions.y;
let anchor = self.ctx.axes.anchor(size);
- self.boxes.push((offset, anchor, layout));
- self.dimensions = new_dimensions;
+ let pos = self.ctx.axes.specialize(
+ self.start
+ + (self.sub.anchor - anchor)
+ + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset)
+ );
+
+ self.actions.add_layout(pos, layout);
+ self.sub.dimensions = new_dimensions;
Ok(())
}
- /// Add multiple sublayouts from a multi-layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
@@ -83,143 +100,106 @@ impl StackLayouter {
Ok(())
}
- /// Add space after the last layout.
pub fn add_space(&mut self, space: Size) {
- if self.dimensions.y + space > self.usable.y {
+ if self.sub.dimensions.y + space > self.sub.usable.y {
self.finish_layout(false);
} else {
- self.dimensions.y += space;
+ self.sub.dimensions.y += space;
}
}
- /// Update the axes in use by this stack layouter.
pub fn set_axes(&mut self, axes: LayoutAxes) {
if axes != self.ctx.axes {
- self.finish_boxes();
+ self.finish_subspace();
+ self.sub = Subspace::new(self.remaining_subspace(), axes);
self.ctx.axes = axes;
- self.usable = self.remains();
- self.dimensions = Size2D::zero();
}
}
- /// Update the followup space to be used by this flex layouter.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.space_is_empty() {
- self.usable = self.ctx.axes.generalize(spaces[0].usable());
- self.active_space = 0;
+ let space = spaces[0];
+ let usable = self.ctx.axes.generalize(space.usable());
+
self.ctx.spaces = spaces;
+ self.space = 0;
+ self.start = space.start();
+ self.sub = Subspace::new(usable, self.ctx.axes);
} else {
- self.ctx.spaces.truncate(self.active_space + 1);
+ self.ctx.spaces.truncate(self.space + 1);
self.ctx.spaces.extend(spaces);
}
}
- /// Finish the layouting.
- ///
- /// The layouter is not consumed by this to prevent ownership problems.
- /// Nevertheless, it should not be used further.
- pub fn finish(&mut self) -> MultiLayout {
- if self.hard || !self.boxes.is_empty() {
- self.finish_layout(false);
- }
- std::mem::replace(&mut self.layouts, MultiLayout::new())
+ pub fn primary_usable(&self) -> Size {
+ self.sub.usable.x
}
- /// Finish the current layout and start a new one in a new space.
- pub fn finish_layout(&mut self, hard: bool) {
- self.finish_boxes();
-
- let space = self.ctx.spaces[self.active_space];
- let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new());
-
- self.layouts.add(Layout {
- dimensions: if self.ctx.shrink_to_fit {
- self.merged_dimensions.padded(space.padding)
- } else {
- space.dimensions
- },
- actions: actions.into_vec(),
- debug_render: true,
- });
-
- let next_space = self.next_space();
- let space = self.ctx.spaces[next_space];
-
- self.merged_dimensions = Size2D::zero();
-
- self.usable = self.ctx.axes.generalize(space.usable());
- self.dimensions = Size2D::zero();
- self.active_space = next_space;
- self.hard = hard;
- }
+ pub fn remaining(&self) -> LayoutSpaces {
+ let mut spaces = smallvec![LayoutSpace {
+ dimensions: self.ctx.axes.specialize(self.remaining_subspace()),
+ padding: SizeBox::zero(),
+ }];
- /// Compose all cached boxes into a layout.
- fn finish_boxes(&mut self) {
- if self.boxes.is_empty() {
- return;
+ for space in &self.ctx.spaces[self.next_space()..] {
+ spaces.push(space.usable_space());
}
- let space = self.ctx.spaces[self.active_space];
- let start = space.start() + Size2D::with_y(self.merged_dimensions.y);
-
- let anchor = self.ctx.axes.anchor(self.usable);
- let factor = if self.ctx.axes.secondary.axis.is_positive() { 1 } else { -1 };
-
- for (offset, layout_anchor, layout) in self.boxes.drain(..) {
- let general_position = anchor - layout_anchor + Size2D::with_y(factor * offset);
- let position = start + self.ctx.axes.specialize(general_position);
-
- self.merged_actions.add_layout(position, layout);
- }
+ spaces
+ }
- let mut dimensions = self.ctx.axes.specialize(self.dimensions);
- let usable = self.ctx.axes.specialize(self.usable);
+ pub fn space_is_empty(&self) -> bool {
+ self.combined_dimensions == Size2D::zero()
+ && self.sub.dimensions == Size2D::zero()
+ }
- if needs_expansion(self.ctx.axes.primary) {
- dimensions.x = usable.x;
- }
+ pub fn in_last_space(&self) -> bool {
+ self.space == self.ctx.spaces.len() - 1
+ }
- if needs_expansion(self.ctx.axes.secondary) {
- dimensions.y = usable.y;
+ pub fn finish(mut self) -> MultiLayout {
+ if self.hard || !self.space_is_empty() {
+ self.finish_layout(false);
}
-
- self.merged_dimensions = merge_sizes(self.merged_dimensions, dimensions);
+ self.layouts
}
- /// The (generalized) usable area of the current space.
- pub fn usable(&self) -> Size2D {
- self.usable
- }
+ pub fn finish_layout(&mut self, hard: bool) {
+ self.finish_subspace();
- /// The remaining usable spaces for new layouts.
- pub fn remaining(&self) -> LayoutSpaces {
- let mut spaces = smallvec![LayoutSpace {
- dimensions: self.ctx.axes.specialize(self.remains()),
- padding: SizeBox::zero(),
- }];
+ let space = self.ctx.spaces[self.space];
+ let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
- for space in &self.ctx.spaces[self.next_space()..] {
- spaces.push(space.usable_space());
- }
+ self.layouts.add(Layout {
+ dimensions: match self.ctx.shrink_to_fit {
+ true => self.combined_dimensions.padded(space.padding),
+ false => space.dimensions,
+ },
+ actions: actions.into_vec(),
+ debug_render: true,
+ });
- spaces
- }
+ self.space = self.next_space();
+ let space = self.ctx.spaces[self.space];
+ let usable = self.ctx.axes.generalize(space.usable());
- fn remains(&self) -> Size2D {
- Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
+ self.hard = hard;
+ self.start = space.start();
+ self.combined_dimensions = Size2D::zero();
+ self.sub = Subspace::new(usable, self.ctx.axes);
}
- pub fn space_is_empty(&self) -> bool {
- self.boxes.is_empty() && self.merged_dimensions == Size2D::zero()
+ fn finish_subspace(&mut self) {
+ let sub_dim = self.ctx.axes.specialize(self.sub.dimensions);
+ self.combined_dimensions = merge_sizes(self.combined_dimensions, sub_dim);
}
- /// Whether this layouter is in its last space.
- pub fn in_last_space(&self) -> bool {
- self.active_space == self.ctx.spaces.len() - 1
+ fn remaining_subspace(&self) -> Size2D {
+ Size2D::new(self.sub.usable.x, self.sub.usable.y - self.sub.dimensions.y)
}
fn next_space(&self) -> usize {
- (self.active_space + 1).min(self.ctx.spaces.len() - 1)
+ (self.space + 1).min(self.ctx.spaces.len() - 1)
}
}
@@ -229,10 +209,3 @@ fn merge_sizes(a: Size2D, b: Size2D) -> Size2D {
y: a.y + b.y
}
}
-
-fn needs_expansion(axis: AlignedAxis) -> bool {
- !matches!(
- (axis.axis.is_positive(), axis.alignment),
- (true, Alignment::Origin) | (false, Alignment::End)
- )
-}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index ae6a15c8..94b8553c 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -134,7 +134,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
}
/// Finish the layout.
- fn finish(mut self) -> LayoutResult<MultiLayout> {
+ fn finish(self) -> LayoutResult<MultiLayout> {
self.flex.finish()
}