summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/stack.rs169
-rw-r--r--src/layout/text.rs1
-rw-r--r--src/size.rs28
4 files changed, 152 insertions, 48 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index d6bb0d6d..fe4d3e08 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -36,8 +36,6 @@ pub type MultiLayout = Vec<Layout>;
pub struct Layout {
/// The size of the box.
pub dimensions: Size2D,
- /// The baseline of the layout (as an offset from the top-left).
- pub baseline: Option<Size>,
/// How to align this layout in a parent container.
pub alignment: LayoutAlignment,
/// The actions composing this layout.
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 2e4f2d3a..369b1c81 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -70,13 +70,17 @@ impl StackLayouter {
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(spacing, _) = self.space.last_spacing {
- self.add_spacing(spacing, SpacingKind::Hard);
+ 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;
}
// Find the first space that fits the layout.
@@ -89,21 +93,7 @@ impl StackLayouter {
self.finish_space(true);
}
- 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);
-
- self.space.size = size.specialized(axes);
- self.space.extra = extra.specialized(axes);
-
- *self.space.usable.secondary_mut(axes) -= dimensions.y;
+ self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
self.space.layouts.push((self.ctx.axes, layout));
self.space.last_spacing = LastSpacing::None;
@@ -124,22 +114,24 @@ impl StackLayouter {
/// Add secondary spacing to the stack.
pub fn add_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
match kind {
- // A hard space is directly added to the sub's size.
+ // A hard space is simply an empty box.
SpacingKind::Hard => {
- // Reduce the spacing such that definitely fits.
+ // Reduce the spacing such that it definitely fits.
spacing.min_eq(self.space.usable.secondary(self.ctx.axes));
+ let dimensions = Size2D::with_y(spacing);
+
+ self.update_metrics(dimensions);
- self.add(Layout {
- dimensions: Size2D::with_y(spacing).specialized(self.ctx.axes),
- baseline: None,
+ self.space.layouts.push((self.ctx.axes, Layout {
+ dimensions: dimensions.specialized(self.ctx.axes),
alignment: LayoutAlignment::default(),
- actions: vec![],
- }).expect("spacing should fit");
+ actions: vec![]
+ }));
self.space.last_spacing = LastSpacing::Hard;
}
- // A hard space is cached if it is not consumed by a hard space or
+ // A soft 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.space.last_spacing {
@@ -155,11 +147,34 @@ impl StackLayouter {
}
}
+ /// 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) {
+ let axes = self.ctx.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);
+
+ self.space.size = size.specialized(axes);
+ self.space.extra = extra.specialized(axes);
+ *self.space.usable.secondary_mut(axes) -= dimensions.y;
+ }
+
/// Change the layouting axes used by this layouter.
///
/// This starts a new subspace (if the axes are actually different from the
/// current ones).
pub fn set_axes(&mut self, axes: LayoutAxes) {
+ // Forget the spacing because it is not relevant anymore.
+ if axes.secondary != self.ctx.axes.secondary {
+ self.space.last_spacing = LastSpacing::Hard;
+ }
+
self.ctx.axes = axes;
}
@@ -221,37 +236,105 @@ impl StackLayouter {
pub fn finish_space(&mut self, hard: bool) {
let space = self.ctx.spaces[self.space.index];
+ // ------------------------------------------------------------------ //
+ // Step 1: Determine the full dimensions of the space.
+ // (Mostly done already while collecting the boxes, but here we
+ // expand if necessary.)
+
let usable = space.usable();
if space.expand.horizontal { self.space.size.x = usable.x; }
if space.expand.vertical { self.space.size.y = usable.y; }
let dimensions = self.space.size.padded(space.padding);
+ // ------------------------------------------------------------------ //
+ // 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 = SizeBox {
+ left: start.x,
+ top: start.y,
+ right: start.x + self.space.size.x,
+ bottom: start.y + self.space.size.y,
+ };
+
+ for (axes, layout) in &self.space.layouts {
+ // First, we 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
+ // layout uses up space from the origin to the end. Thus, it reduces
+ // the usable space for following layouts at it's origin by its
+ // extent along the secondary axis.
+ *bound.secondary_origin_mut(*axes)
+ += axes.secondary.factor() * layout.dimensions.secondary(*axes);
+ }
+
+ // ------------------------------------------------------------------ //
+ // 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;
+
+ for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
+ let (axes, layout) = entry;
+
+ // 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;
+
+ // Then, we add this layout's secondary extent to the accumulator.
+ extent += layout.dimensions.secondary(*axes);
+ }
+
+ // ------------------------------------------------------------------ //
+ // Step 4: Align each layout in its bounding box and collect everything
+ // into a single finished layout.
+
let mut actions = LayoutActions::new();
actions.add(LayoutAction::DebugBox(dimensions));
- let mut cursor = space.start();
- for (axes, layout) in std::mem::replace(&mut self.space.layouts, vec![]) {
+ 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.primary;
+ 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()),
+ };
- let primary_usable = self.space.size.primary(axes) - cursor.primary(axes);
+ let position = Size2D::new(bound.left, bound.top)
+ + offsets.specialized(axes);
- 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),
- };
+ println!("pos: {}", position);
+ println!("usable: {}", usable);
+ println!("size: {}", size);
- actions.add_layout(position.specialized(axes), layout);
- *cursor.secondary_mut(axes) += size.y;
+ actions.add_layout(position, layout);
}
self.layouts.push(Layout {
dimensions,
- baseline: None,
alignment: self.ctx.alignment,
actions: actions.to_vec(),
});
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 30620c0b..5ecc40cc 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -73,7 +73,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
Ok(Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size),
- baseline: None,
alignment: self.ctx.alignment,
actions: self.actions.to_vec(),
})
diff --git a/src/size.rs b/src/size.rs
index 71c5458f..44e1e642 100644
--- a/src/size.rs
+++ b/src/size.rs
@@ -6,7 +6,7 @@ use std::iter::Sum;
use std::ops::*;
use std::str::FromStr;
-use crate::layout::{LayoutAxes, Alignment};
+use crate::layout::{LayoutAxes, Axis, Alignment};
/// A general space type.
#[derive(Copy, Clone, PartialEq)]
@@ -101,7 +101,6 @@ impl Size {
(true, End) | (false, Origin) => *self,
}
}
-
}
impl Size2D {
@@ -219,6 +218,11 @@ impl Size2D {
self.x.min_eq(other.x);
self.y.min_eq(other.y);
}
+
+ /// Swap the two dimensions.
+ pub fn swap(&mut self) {
+ std::mem::swap(&mut self.x, &mut self.y);
+ }
}
impl SizeBox {
@@ -250,6 +254,26 @@ impl SizeBox {
SizeBox { left: value, top: value, right: value, bottom: value }
}
+ /// Access the origin direction on the secondary axis of this box.
+ pub fn secondary_origin_mut(&mut self, axes: LayoutAxes) -> &mut Size {
+ match axes.secondary {
+ Axis::LeftToRight => &mut self.left,
+ Axis::RightToLeft => &mut self.right,
+ Axis::TopToBottom => &mut self.top,
+ Axis::BottomToTop => &mut self.bottom,
+ }
+ }
+
+ /// Access the end direction on the secondary axis of this box.
+ pub fn secondary_end_mut(&mut self, axes: LayoutAxes) -> &mut Size {
+ match axes.secondary {
+ Axis::LeftToRight => &mut self.right,
+ Axis::RightToLeft => &mut self.left,
+ Axis::TopToBottom => &mut self.bottom,
+ Axis::BottomToTop => &mut self.top,
+ }
+ }
+
/// Set the `left` and `right` values.
pub fn set_all(&mut self, value: Size) {
*self = SizeBox::with_all(value);