summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-01 19:18:06 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-01 19:18:38 +0100
commitace57c34206a13b4bc3885b944cc51e274f30b0f (patch)
tree2f1e1e8e4ac5c3867492d5a59c49045058e16123 /src
parent5782b82770f6923677942c3b4e2bf4f7258e47d8 (diff)
First half of stack update 🌓
Diffstat (limited to 'src')
-rw-r--r--src/func/mod.rs12
-rw-r--r--src/layout/flex.rs6
-rw-r--r--src/layout/mod.rs19
-rw-r--r--src/layout/stack.rs238
-rw-r--r--src/layout/text.rs3
-rw-r--r--src/layout/tree.rs41
-rw-r--r--src/library/spacing.rs4
7 files changed, 202 insertions, 121 deletions
diff --git a/src/func/mod.rs b/src/func/mod.rs
index 126fd824..33a6c756 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -12,8 +12,9 @@ pub mod helpers;
/// Useful imports for creating your own functions.
pub mod prelude {
pub use crate::func::{Command, CommandList, Function};
- pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace};
- pub use crate::layout::{LayoutAxes, Axis, Alignment};
+ pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext};
+ pub use crate::layout::{LayoutSpace, LayoutSpaces, SpacingKind};
+ pub use crate::layout::{LayoutAxes, Axis, AxisKind, LayoutAlignment, Alignment};
pub use crate::layout::{LayoutError, LayoutResult};
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
@@ -96,19 +97,16 @@ pub enum Command<'a> {
Add(Layout),
AddMultiple(MultiLayout),
-
- AddPrimarySpace(Size),
- AddSecondarySpace(Size),
+ AddSpacing(Size, SpacingKind, AxisKind),
FinishLine,
FinishRun,
FinishSpace,
-
BreakParagraph,
SetTextStyle(TextStyle),
SetPageStyle(PageStyle),
-
+ SetAlignment(LayoutAlignment),
SetAxes(LayoutAxes),
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 96b2aa85..afca23ec 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -62,7 +62,7 @@ impl PartialLine {
pub struct FlexContext {
pub spaces: LayoutSpaces,
pub axes: LayoutAxes,
- pub expand: bool,
+ pub alignment: LayoutAlignment,
pub flex_spacing: Size,
}
@@ -110,7 +110,7 @@ impl FlexLayouter {
if !self.run_is_empty() {
self.finish_run()?;
}
- Ok(self.stack.add_space(space, kind))
+ Ok(self.stack.add_spacing(space, kind))
}
pub fn set_axes(&mut self, axes: LayoutAxes) {
@@ -179,7 +179,7 @@ impl FlexLayouter {
debug_render: false,
})?;
- self.stack.add_space(self.flex_spacing, SpaceKind::Independent);
+ self.stack.add_spacing(self.flex_spacing, SpaceKind::Independent);
let remaining = self.axes.specialize(Size2D {
x: self.part.usable
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 31064d40..f45ed10a 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -59,7 +59,7 @@ pub struct LayoutContext<'a, 'p> {
pub spaces: LayoutSpaces,
/// The initial axes along which content is laid out.
pub axes: LayoutAxes,
- /// The alignment for the two axes.
+ /// The alignment of the finished layout.
pub alignment: LayoutAlignment,
}
@@ -71,12 +71,12 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
pub struct LayoutSpace {
/// The maximum size of the box to layout in.
pub dimensions: Size2D,
+ /// Padding that should be respected on each side.
+ pub padding: SizeBox,
/// 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
/// vertical and horizontal axis.
pub expand: (bool, bool),
- /// Padding that should be respected on each side.
- pub padding: SizeBox,
}
impl LayoutSpace {
@@ -95,8 +95,8 @@ impl LayoutSpace {
pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace {
dimensions: self.usable(),
- expand: (false, false),
padding: SizeBox::zero(),
+ expand: (false, false),
}
}
}
@@ -110,6 +110,10 @@ pub struct LayoutAxes {
impl LayoutAxes {
pub fn new(primary: Axis, secondary: Axis) -> LayoutAxes {
+ if primary.is_horizontal() == secondary.is_horizontal() {
+ panic!("LayoutAxes::new: invalid parallel axes {:?} and {:?}", primary, secondary);
+ }
+
LayoutAxes { primary, secondary }
}
@@ -134,6 +138,13 @@ impl LayoutAxes {
}
}
+/// The two kinds of axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum AxisKind {
+ Primary,
+ Secondary,
+}
+
/// Directions along which content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Axis {
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index fd88ce98..793c2044 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -11,29 +11,42 @@ pub struct StackLayouter {
ctx: StackContext,
/// The output layouts.
layouts: MultiLayout,
- /// The full layout space.
+ /// The currently active layout space.
space: Space,
- /// The currently active subspace.
+ /// The remaining subspace of the active space. Whenever the layouting axes
+ /// change a new subspace is started.
sub: Subspace,
}
+/// The context for stack layouting.
+///
+/// See [`LayoutContext`] for details about the fields.
+#[derive(Debug, Clone)]
+pub struct StackContext {
+ pub spaces: LayoutSpaces,
+ pub axes: LayoutAxes,
+ pub alignment: LayoutAlignment,
+}
+
+/// A layout space composed of subspaces which can have different axes and
+/// alignments.
#[derive(Debug, Clone)]
struct Space {
/// The index of this space in the list of spaces.
index: usize,
/// Whether to add the layout for this space even if it would be empty.
hard: bool,
- /// The layouting actions accumulated from the subspaces.
- actions: LayoutActionList,
- /// The used size of this space from the top-left corner to
- /// the bottomright-most point of used space (specialized).
- combined_dimensions: Size2D,
+ /// The so-far accumulated subspaces.
+ spaces: 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).
@@ -43,45 +56,19 @@ struct Subspace {
/// - `y` being the total extent of all boxes and space in the secondary
/// direction.
size: Size2D,
- /// The so-far accumulated (offset, anchor, box) triples.
- boxes: Vec<(Size, Size, Layout)>,
- /// The last added spacing if the last was spacing.
+ /// The so-far accumulated layouts.
+ layouts: Vec<LayoutEntry>,
+ /// The last added spacing if the last added thing was spacing.
last_spacing: LastSpacing,
}
-impl Space {
- fn new(index: usize, hard: bool) -> Space {
- Space {
- index,
- hard,
- actions: LayoutActionList::new(),
- combined_dimensions: Size2D::zero(),
- }
- }
-}
-
-impl Subspace {
- fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace {
- Subspace {
- origin,
- anchor: axes.anchor(usable),
- factor: axes.secondary.axis.factor(),
- boxes: vec![],
- usable: axes.generalize(usable),
- dimensions: Size2D::zero(),
- space: LastSpacing::Forbidden,
- }
- }
-}
-
-/// The context for stack layouting.
-///
-/// See [`LayoutContext`] for details about the fields.
+/// A single layout in a subspace.
#[derive(Debug, Clone)]
-pub struct StackContext {
- pub spaces: LayoutSpaces,
- pub axes: LayoutAxes,
- pub expand: bool,
+struct LayoutEntry {
+ /// The offset of this box on the secondary axis.
+ offset: Size,
+ /// The layout itself.
+ layout: Layout,
}
impl StackLayouter {
@@ -94,41 +81,61 @@ impl StackLayouter {
ctx,
layouts: MultiLayout::new(),
space: Space::new(0, true),
- sub: Subspace::new(space.start(), space.usable(), axes),
+ sub: Subspace::new(axes, Alignment::Origin, space.start(), space.usable()),
}
}
+ /// Add a layout to the stack.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
- if let LastSpacing::Soft(space) = self.sub.space {
- self.add_space(space, SpaceKind::Hard);
+ if layout.alignment.secondary != self.sub.alignment {
+ // self.finish_subspace();
+ // finish sub and start new with layout's alignment
}
- let size = self.ctx.axes.generalize(layout.dimensions);
+ // Add a cached soft space if there is one.
+ if let LastSpacing::Soft(space, _) = self.sub.last_spacing {
+ self.add_spacing(space, SpacingKind::Hard);
+ }
- let mut new_dimensions = Size2D {
- x: crate::size::max(self.sub.dimensions.x, size.x),
- y: self.sub.dimensions.y + size.y
+ // 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
};
- while !self.sub.usable.fits(new_dimensions) {
+ // Find the first (sub-)space that fits the layout.
+ while !self.sub.usable.fits(new_size) {
if self.space_is_last() && self.space_is_empty() {
lerr!("box does not fit into stack");
}
self.finish_space(true);
- new_dimensions = size;
+ new_size = size;
}
- let offset = self.sub.dimensions.y;
- let anchor = self.ctx.axes.primary.anchor(size.x);
+ // 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,
+ });
+
+ // The new size of the subspace is the previously calculated
+ // combination.
+ self.sub.size = new_size;
- self.sub.boxes.push((offset, anchor, layout));
- self.sub.dimensions = new_dimensions;
- self.sub.space = LastSpacing::Allowed;
+ // Since the last item was a box, last spacing is reset to `None`.
+ self.sub.last_spacing = LastSpacing::None;
Ok(())
}
+ /// Add multiple layouts to the stack.
+ ///
+ /// This function simply calls `add` for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
@@ -136,33 +143,55 @@ impl StackLayouter {
Ok(())
}
- pub fn add_space(&mut self, space: Size, kind: SpaceKind) {
- if kind == SpaceKind::Soft {
- if self.sub.space != LastSpacing::Forbidden {
- self.sub.space = LastSpacing::Soft(space);
- }
- } else {
- if self.sub.dimensions.y + space > self.sub.usable.y {
- self.sub.dimensions.y = self.sub.usable.y;
- } else {
- self.sub.dimensions.y += space;
+ /// Add secondary spacing to the stack.
+ pub fn add_spacing(&mut self, space: 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;
+ }
+
+ self.sub.last_spacing = LastSpacing::Hard;
}
- if kind == SpaceKind::Hard {
- self.sub.space = LastSpacing::Forbidden;
+ // 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 {
+ LastSpacing::None => true,
+ LastSpacing::Soft(_, prev) if level < prev => true,
+ _ => false,
+ };
+
+ if consumes {
+ self.sub.last_spacing = LastSpacing::Soft(space, level);
+ }
}
}
}
+ /// Change the layouting axis 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) {
if axes != self.ctx.axes {
self.finish_subspace();
+
let (origin, usable) = self.remaining_subspace();
+ self.sub = Subspace::new(axes, Alignment::Origin, origin, usable);
self.ctx.axes = axes;
- self.sub = Subspace::new(origin, usable, axes);
}
}
+ /// Change the layouting spaces to use.
+ ///
+ /// If `replace_empty` is true, the current space is replaced if there are
+ /// no boxes laid into it yet. Otherwise, only the followup spaces are
+ /// replaced.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.space_is_empty() {
self.ctx.spaces = spaces;
@@ -173,10 +202,13 @@ 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 mut spaces = smallvec![LayoutSpace {
dimensions: self.remaining_subspace().1,
padding: SizeBox::zero(),
+ expand: (false, false),
}];
for space in &self.ctx.spaces[self.next_space()..] {
@@ -186,20 +218,24 @@ impl StackLayouter {
spaces
}
+ /// The usable size along the primary axis.
pub fn primary_usable(&self) -> Size {
self.sub.usable.x
}
+ /// Whether the current layout space (not subspace) is empty.
pub fn space_is_empty(&self) -> bool {
- self.space.combined_dimensions == Size2D::zero()
- && self.space.actions.is_empty()
- && self.sub.dimensions == Size2D::zero()
+ self.sub.layouts.is_empty()
+ && self.sub.size == Size2D::zero()
+ && self.space.spaces.is_empty()
}
+ /// Whether the current layout space is the last is the followup list.
pub fn space_is_last(&self) -> bool {
self.space.index == self.ctx.spaces.len() - 1
}
+ /// Compute the finished multi-layout.
pub fn finish(mut self) -> MultiLayout {
if self.space.hard || !self.space_is_empty() {
self.finish_space(false);
@@ -212,29 +248,19 @@ impl StackLayouter {
let space = self.ctx.spaces[self.space.index];
- self.layouts.add(Layout {
+ self.layouts.push(Layout {
dimensions: match self.ctx.expand {
true => space.dimensions,
false => self.space.combined_dimensions.padded(space.padding),
},
- actions: self.space.actions.to_vec(),
- debug_render: true,
+ baseline: None,
+ alignment: self.ctx.alignment,
+ actions: actions.to_vec(),
});
self.start_space(self.next_space(), hard);
}
- fn start_space(&mut self, space: usize, hard: bool) {
- self.space = Space::new(space, hard);
-
- let space = self.ctx.spaces[space];
- self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes);
- }
-
- fn next_space(&self) -> usize {
- (self.space.index + 1).min(self.ctx.spaces.len() - 1)
- }
-
fn finish_subspace(&mut self) {
let factor = self.ctx.axes.secondary.axis.factor();
let anchor =
@@ -264,6 +290,18 @@ impl StackLayouter {
self.space.combined_dimensions.max_eq(origin - space.start() + dimensions);
}
+ /// 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());
+ }
+
+ /// The remaining sub
fn remaining_subspace(&self) -> (Size2D, Size2D) {
let new_origin = self.sub.origin + match self.ctx.axes.secondary.axis.is_positive() {
true => self.ctx.axes.specialize(Size2D::with_y(self.sub.dimensions.y)),
@@ -277,4 +315,32 @@ impl StackLayouter {
(new_origin, new_usable)
}
+
+ fn next_space(&self) -> usize {
+ (self.space.index + 1).min(self.ctx.spaces.len() - 1)
+ }
+}
+
+impl Space {
+ fn new(index: usize, hard: bool) -> Space {
+ Space {
+ index,
+ hard,
+ spaces: 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![],
+ last_spacing: LastSpacing::Hard,
+ }
+ }
}
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 3ca826ca..343127e3 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -11,6 +11,7 @@ use crate::size::{Size, Size2D};
pub struct TextContext<'a, 'p> {
pub loader: &'a SharedFontLoader<'p>,
pub style: &'a TextStyle,
+ pub alignment: LayoutAlignment,
}
/// Layouts text into a box.
@@ -72,6 +73,8 @@ 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/layout/tree.rs b/src/layout/tree.rs
index 9a818963..efa0c7b7 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -19,10 +19,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter {
flex: FlexLayouter::new(FlexContext {
- flex_spacing: flex_spacing(&ctx.style.text),
spaces: ctx.spaces.clone(),
axes: ctx.axes,
- expand: ctx.expand,
+ alignment: ctx.alignment,
+ flex_spacing: flex_spacing(&ctx.style.text),
}),
style: ctx.style.clone(),
ctx,
@@ -52,6 +52,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let layout = layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
+ alignment: self.ctx.alignment,
})?;
Ok(self.flex.add(layout))
@@ -90,23 +91,25 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
}
fn execute(&mut self, command: Command) -> LayoutResult<()> {
- match command {
- Command::LayoutTree(tree) => self.layout(tree)?,
+ use Command::*;
- Command::Add(layout) => self.flex.add(layout),
- Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
-
- Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, SpacingKind::Hard),
- Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, SpacingKind::Hard)?,
+ match command {
+ LayoutTree(tree) => self.layout(tree)?,
- Command::FinishLine => self.flex.add_break(),
- Command::FinishRun => { self.flex.finish_run()?; },
- Command::FinishSpace => self.flex.finish_space(true)?,
+ Add(layout) => self.flex.add(layout),
+ AddMultiple(layouts) => self.flex.add_multiple(layouts),
+ AddSpacing(space, kind, axis) => match axis {
+ AxisKind::Primary => self.flex.add_primary_space(space, kind),
+ AxisKind::Secondary => self.flex.add_secondary_space(space, kind)?,
+ }
- Command::BreakParagraph => self.layout_paragraph()?,
+ FinishLine => self.flex.add_break(),
+ FinishRun => { self.flex.finish_run()?; },
+ FinishSpace => self.flex.finish_space(true)?,
+ BreakParagraph => self.layout_paragraph()?,
- Command::SetTextStyle(style) => self.style.text = style,
- Command::SetPageStyle(style) => {
+ SetTextStyle(style) => self.style.text = style,
+ SetPageStyle(style) => {
if !self.ctx.top_level {
lerr!("page style cannot only be altered in the top-level context");
}
@@ -115,13 +118,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
self.flex.set_spaces(smallvec![
LayoutSpace {
dimensions: style.dimensions,
- expand: (true, true),
padding: style.margins,
+ expand: (true, true),
}
], true);
- },
-
- Command::SetAxes(axes) => {
+ }
+ SetAlignment(alignment) => self.ctx.alignment = alignment,
+ SetAxes(axes) => {
self.flex.set_axes(axes);
self.ctx.axes = axes;
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 869a6227..47fe9fff 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -65,7 +65,7 @@ enum Spacing {
// FIXME: h != primary and v != secondary.
space_func!(HorizontalSpace, "📖 `h`: Adds horizontal whitespace.",
- space => AddPrimarySpace(space));
+ space => AddSpacing(space, SpacingKind::Hard, AxisKind::Primary));
space_func!(VerticalSpace, "📑 `v`: Adds vertical whitespace.",
- space => AddSecondarySpace(space));
+ space => AddSpacing(space, SpacingKind::Hard, AxisKind::Secondary));