summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/actions.rs4
-rw-r--r--src/layout/flex.rs4
-rw-r--r--src/layout/mod.rs67
-rw-r--r--src/layout/stack.rs327
-rw-r--r--src/layout/text.rs2
-rw-r--r--src/layout/tree.rs2
6 files changed, 133 insertions, 273 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index 01abc0ba..b0d2c21d 100644
--- a/src/layout/actions.rs
+++ b/src/layout/actions.rs
@@ -68,8 +68,8 @@ impl LayoutActions {
pub fn new() -> LayoutActions {
LayoutActions {
actions: vec![],
- origin: Size2D::zero(),
- active_font: (std::usize::MAX, Size::zero()),
+ origin: Size2D::ZERO,
+ active_font: (std::usize::MAX, Size::ZERO),
next_pos: None,
next_font: None,
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index d984860a..fc1a09c0 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -33,7 +33,7 @@ impl FlexLine {
FlexLine {
usable,
actions: LayoutActions::new(),
- combined_dimensions: Size2D::zero(),
+ combined_dimensions: Size2D::ZERO,
}
}
}
@@ -51,7 +51,7 @@ impl PartialLine {
PartialLine {
usable,
content: vec![],
- dimensions: Size2D::zero(),
+ dimensions: Size2D::ZERO,
space: LastSpacing::Hard,
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index c34d881e..d6bb0d6d 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,7 +1,6 @@
//! The core layouting engine.
use std::io::{self, Write};
-
use smallvec::SmallVec;
use toddle::query::{FontClass, SharedFontLoader};
@@ -91,7 +90,7 @@ pub struct LayoutSpace {
/// Whether to expand the dimensions of the resulting layout to the full
/// dimensions of this space or to shrink them to fit the content for the
/// horizontal and vertical axis.
- pub expand: (bool, bool),
+ pub expand: LayoutExpansion,
}
impl LayoutSpace {
@@ -110,12 +109,25 @@ impl LayoutSpace {
pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace {
dimensions: self.usable(),
- padding: SizeBox::zero(),
- expand: (false, false),
+ padding: SizeBox::ZERO,
+ expand: LayoutExpansion::new(false, false),
}
}
}
+/// Whether to fit to content or expand to the space's size.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct LayoutExpansion {
+ pub horizontal: bool,
+ pub vertical: bool,
+}
+
+impl LayoutExpansion {
+ pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
+ LayoutExpansion { horizontal, vertical }
+ }
+}
+
/// The axes along which the content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAxes {
@@ -132,28 +144,8 @@ impl LayoutAxes {
LayoutAxes { primary, secondary }
}
- /// Returns the generalized version of a `Size2D` dependent on
- /// the layouting axes, that is:
- /// - The x coordinate describes the primary axis instead of the horizontal one.
- /// - The y coordinate describes the secondary axis instead of the vertical one.
- pub fn generalize(&self, size: Size2D) -> Size2D {
- if self.primary.is_horizontal() {
- size
- } else {
- Size2D { x: size.y, y: size.x }
- }
- }
-
- /// Returns the specialized version of this generalized Size2D.
- /// (Inverse to `generalized`).
- pub fn specialize(&self, size: Size2D) -> Size2D {
- // In fact, generalized is its own inverse. For reasons of clarity
- // at the call site, we still have this second function.
- self.generalize(size)
- }
-
/// Return the specified generic axis.
- pub fn get_generic(&self, axis: GenericAxisKind) -> Axis {
+ pub fn generic(&self, axis: GenericAxisKind) -> Axis {
match axis {
GenericAxisKind::Primary => self.primary,
GenericAxisKind::Secondary => self.secondary,
@@ -161,8 +153,8 @@ impl LayoutAxes {
}
/// Return the specified specific axis.
- pub fn get_specific(&self, axis: SpecificAxisKind) -> Axis {
- self.get_generic(axis.generic(*self))
+ pub fn specific(&self, axis: SpecificAxisKind) -> Axis {
+ self.generic(axis.generic(*self))
}
/// Returns the generic axis kind which is the horizontal axis.
@@ -222,6 +214,15 @@ impl LayoutAxes {
}
}
+impl Default for LayoutAxes {
+ fn default() -> LayoutAxes {
+ LayoutAxes {
+ primary: Axis::LeftToRight,
+ secondary: Axis::TopToBottom,
+ }
+ }
+}
+
/// Directions along which content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Axis {
@@ -318,7 +319,7 @@ impl SpecificAxisKind {
}
/// The place to put a layout in a container.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAlignment {
pub primary: Alignment,
pub secondary: Alignment,
@@ -331,7 +332,7 @@ impl LayoutAlignment {
}
/// Where to align content.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Alignment {
Origin,
Center,
@@ -349,6 +350,12 @@ impl Alignment {
}
}
+impl Default for Alignment {
+ fn default() -> Alignment {
+ Alignment::Origin
+ }
+}
+
/// Whitespace between boxes with different interaction properties.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SpacingKind {
@@ -377,7 +384,7 @@ impl LastSpacing {
fn soft_or_zero(&self) -> Size {
match self {
LastSpacing::Soft(space, _) => *space,
- _ => Size::zero(),
+ _ => Size::ZERO,
}
}
}
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<Subspace>,
-}
-
-/// 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<LayoutEntry>,
+ /// 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,
}
}
diff --git a/src/layout/text.rs b/src/layout/text.rs
index b5ff192e..30620c0b 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -42,7 +42,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
actions: LayoutActions::new(),
buffer: String::new(),
active_font: std::usize::MAX,
- width: Size::zero(),
+ width: Size::ZERO,
classes: ctx.style.classes.clone(),
}
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 94a50eea..d620739d 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -116,7 +116,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
LayoutSpace {
dimensions: style.dimensions,
padding: style.margins,
- expand: (true, true),
+ expand: LayoutExpansion::new(true, true),
}
], true);
}