summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-20 17:28:58 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-20 17:31:52 +0100
commitf24e9b44e0ceb19be6f4e16af2d22815e9ccf5b7 (patch)
tree9e520ee1f8b802973aa5b568c11fafc42e6426a8 /src/layout
parent1dafe2c2ea0828bb075fdbb0da663967f7b5b2b9 (diff)
Refined expansion model 🔛
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/flex.rs185
-rw-r--r--src/layout/mod.rs36
-rw-r--r--src/layout/stacked.rs100
-rw-r--r--src/layout/tree.rs27
4 files changed, 190 insertions, 158 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 4985fed2..64d48c7d 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -43,7 +43,7 @@ struct PartialLine {
usable: Size,
content: Vec<(Size, Layout)>,
dimensions: Size2D,
- space: Option<(Size, bool)>,
+ space: Option<Size>,
}
impl PartialLine {
@@ -64,7 +64,7 @@ impl PartialLine {
pub struct FlexContext {
pub spaces: LayoutSpaces,
pub axes: LayoutAxes,
- pub shrink_to_fit: bool,
+ pub expand: bool,
pub flex_spacing: Size,
}
@@ -74,10 +74,11 @@ impl FlexLayouter {
let stack = StackLayouter::new(StackContext {
spaces: ctx.spaces,
axes: ctx.axes,
- shrink_to_fit: ctx.shrink_to_fit,
+ expand: ctx.expand,
});
let usable = stack.primary_usable();
+
FlexLayouter {
stack,
@@ -107,9 +108,11 @@ impl FlexLayouter {
self.units.push(FlexUnit::Space(space, soft));
}
- pub fn add_secondary_space(&mut self, space: Size) -> LayoutResult<()> {
- self.finish_run()?;
- Ok(self.stack.add_space(space))
+ pub fn add_secondary_space(&mut self, space: Size, soft: bool) -> LayoutResult<()> {
+ if !self.run_is_empty() {
+ self.finish_run()?;
+ }
+ Ok(self.stack.add_space(space, soft))
}
pub fn set_axes(&mut self, axes: LayoutAxes) {
@@ -120,28 +123,26 @@ impl FlexLayouter {
if replace_empty && self.run_is_empty() && self.stack.space_is_empty() {
self.stack.set_spaces(spaces, true);
self.start_run();
-
- // let usable = self.stack.primary_usable();
- // self.line = FlexLine::new(usable);
-
- // // self.total_usable = self.stack.primary_usable();
- // // self.usable = self.total_usable;
- // // self.space = None;
} else {
self.stack.set_spaces(spaces, false);
}
}
- pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
- let mut future = self.clone();
- future.finish_run()?;
+ pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, Option<LayoutSpaces>)> {
+ if self.run_is_empty() {
+ Ok((self.stack.remaining(), None))
+ } else {
+ let mut future = self.clone();
+ let remaining_run = future.finish_run()?;
+
+ let stack_spaces = future.stack.remaining();
+ let mut flex_spaces = stack_spaces.clone();
+ flex_spaces[0].dimensions.x = remaining_run.x;
+ flex_spaces[0].dimensions.y += remaining_run.y;
- let stack_spaces = future.stack.remaining();
- let mut flex_spaces = stack_spaces.clone();
- flex_spaces[0].dimensions.x = future.last_run_remaining.x;
- flex_spaces[0].dimensions.y += future.last_run_remaining.y;
+ Ok((flex_spaces, Some(stack_spaces)))
+ }
- Ok((flex_spaces, stack_spaces))
}
pub fn run_is_empty(&self) -> bool {
@@ -149,7 +150,7 @@ impl FlexLayouter {
}
pub fn run_last_is_space(&self) -> bool {
- matches!(self.units.last(), Some(FlexUnit::Space(_)))
+ matches!(self.units.last(), Some(FlexUnit::Space(_, _)))
}
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
@@ -158,32 +159,65 @@ impl FlexLayouter {
}
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
- self.finish_run()?;
+ if !self.run_is_empty() {
+ self.finish_run()?;
+ }
Ok(self.stack.finish_space(hard))
}
- pub fn finish_run(&mut self) -> LayoutResult<()> {
+ pub fn finish_run(&mut self) -> LayoutResult<Size2D> {
let units = std::mem::replace(&mut self.units, vec![]);
for unit in units {
match unit {
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
- FlexUnit::Space(space, soft) => {
- self.layout_space();
- self.space = Some(space);
- }
-
- FlexUnit::Break => {
- self.space = None;
- self.finish_line()?;
- },
-
+ FlexUnit::Space(space, soft) => self.layout_space(space, soft),
FlexUnit::SetAxes(axes) => self.layout_set_axes(axes),
+ FlexUnit::Break => self.layout_break(),
}
}
- self.finish_line()?;
+ self.finish_line()
+ }
- Ok(())
+ fn finish_line(&mut self) -> LayoutResult<Size2D> {
+ self.finish_partial_line();
+
+ self.stack.add(Layout {
+ dimensions: self.axes.specialize(self.line.combined_dimensions),
+ actions: self.line.actions.into_vec(),
+ debug_render: false,
+ })?;
+
+ let remaining = self.axes.specialize(Size2D {
+ x: self.line.usable - self.line.combined_dimensions.x,
+ y: self.line.combined_dimensions.y,
+ });
+
+ self.line = FlexLine::new(self.stack.primary_usable());
+
+ Ok(remaining)
+ }
+
+ fn finish_partial_line(&mut self) {
+ let part = self.line.part;
+
+ let factor = self.axes.primary.axis.factor();
+ let anchor =
+ self.axes.primary.anchor(self.line.usable)
+ - self.axes.primary.anchor(part.dimensions.x);
+
+ for (offset, layout) in part.content {
+ let pos = self.axes.specialize(Size2D::with_x(anchor + factor * offset));
+ self.line.actions.add_layout(pos, layout);
+ }
+
+ self.line.combined_dimensions.x.max_eq(part.dimensions.x);
+ self.line.part = PartialLine::new(self.line.usable - part.dimensions.x);
+ }
+
+ fn start_run(&mut self) {
+ let usable = self.stack.primary_usable();
+ self.line = FlexLine::new(usable);
}
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
@@ -215,7 +249,7 @@ impl FlexLayouter {
Ok(())
}
- fn layout_space(&mut self) {
+ fn layout_space(&mut self, space: Size, soft: bool) {
if let Some(space) = self.space.take() {
if self.run.size.x > Size::zero() && self.run.size.x + space <= self.usable {
self.run.size.x += space;
@@ -227,19 +261,19 @@ impl FlexLayouter {
if axes.primary != self.axes.primary {
self.finish_partial_line();
- self.usable = match axes.primary.alignment {
- Alignment::Origin =>
- if self.max_extent == Size::zero() {
- self.total_usable
- } else {
- Size::zero()
- },
- Alignment::Center => crate::size::max(
- self.total_usable - 2 * self.max_extent,
- Size::zero()
- ),
- Alignment::End => self.total_usable - self.max_extent,
- };
+ // self.usable = match axes.primary.alignment {
+ // Alignment::Origin =>
+ // if self.max_extent == Size::zero() {
+ // self.total_usable
+ // } else {
+ // Size::zero()
+ // },
+ // Alignment::Center => crate::size::max(
+ // self.total_usable - 2 * self.max_extent,
+ // Size::zero()
+ // ),
+ // Alignment::End => self.total_usable - self.max_extent,
+ // };
}
if axes.secondary != self.axes.secondary {
@@ -249,57 +283,8 @@ impl FlexLayouter {
self.axes = axes;
}
- fn finish_line(&mut self) -> LayoutResult<()> {
- self.finish_partial_line();
-
- if self.merged_dimensions.y == Size::zero() {
- return Ok(());
- }
-
- let actions = std::mem::replace(&mut self.merged_actions, LayoutActionList::new());
- self.stack.add(Layout {
- dimensions: self.axes.specialize(self.merged_dimensions),
- actions: actions.into_vec(),
- debug_render: false,
- })?;
-
- self.merged_dimensions = Size2D::zero();
- self.max_extent = Size::zero();
- self.usable = self.total_usable;
-
- Ok(())
- }
-
- fn finish_partial_line(&mut self) {
- if self.run.content.is_empty() {
- return;
- }
-
- let factor = if self.axes.primary.axis.is_positive() { 1 } else { -1 };
- let anchor = self.axes.primary.anchor(self.total_usable)
- - self.axes.primary.anchor(self.run.size.x);
-
- self.max_extent = crate::size::max(self.max_extent, anchor + factor * self.run.size.x);
-
- for (offset, layout) in self.run.content.drain(..) {
- let general_position = Size2D::with_x(anchor + factor * offset);
- let position = self.axes.specialize(general_position);
-
- self.merged_actions.add_layout(position, layout);
- }
-
- self.merged_dimensions.x = match self.axes.primary.alignment {
- Alignment::Origin => self.run.size.x,
- Alignment::Center | Alignment::End => self.total_usable,
- };
-
- self.merged_dimensions.y = crate::size::max(
- self.merged_dimensions.y,
- self.run.size.y + self.flex_spacing,
- );
+ fn layout_break(&mut self) {
- self.last_run_remaining = Size2D::new(self.size_left(), self.merged_dimensions.y);
- self.run.size = Size2D::zero();
}
fn size_left(&self) -> Size {
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 13b63568..2c725e1d 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -147,9 +147,9 @@ pub struct LayoutContext<'a, 'p> {
/// The axes to flow on.
pub axes: LayoutAxes,
- /// Whether to shrink the spaces to fit the content or to keep
- /// the original dimensions.
- pub shrink_to_fit: bool,
+ /// Whether layouts should expand to the full dimensions of the space
+ /// they lie on or whether should tightly fit the content.
+ pub expand: bool,
}
/// A possibly stack-allocated vector of layout spaces.
@@ -219,6 +219,14 @@ impl LayoutAxes {
pub fn anchor(&self, space: Size2D) -> Size2D {
Size2D::new(self.primary.anchor(space.x), self.secondary.anchor(space.y))
}
+
+ /// This axes with `expand` set to the given value for both axes.
+ pub fn expanding(&self, expand: bool) -> LayoutAxes {
+ LayoutAxes {
+ primary: self.primary.expanding(expand),
+ secondary: self.secondary.expanding(expand),
+ }
+ }
}
/// An axis with an alignment.
@@ -226,17 +234,13 @@ impl LayoutAxes {
pub struct AlignedAxis {
pub axis: Axis,
pub alignment: Alignment,
+ pub expand: bool,
}
impl AlignedAxis {
- /// Returns an aligned axis if the alignment is compatible with the axis.
- pub fn new(axis: Axis, alignment: Alignment) -> AlignedAxis {
- AlignedAxis { axis, alignment }
- }
-
- /// The pair of axis and alignment.
- pub fn pair(&self) -> (Axis, Alignment) {
- (self.axis, self.alignment)
+ /// Creates an aligned axis from its three components.
+ pub fn new(axis: Axis, alignment: Alignment, expand: bool) -> AlignedAxis {
+ AlignedAxis { axis, alignment, expand }
}
/// The position of the anchor specified by this axis on the given line.
@@ -248,6 +252,16 @@ impl AlignedAxis {
(true, End) | (false, Origin) => line,
}
}
+
+ /// This axis with `expand` set to the given value.
+ pub fn expanding(&self, expand: bool) -> AlignedAxis {
+ AlignedAxis { expand, ..*self }
+ }
+
+ /// Whether this axis needs expansion.
+ pub fn needs_expansion(&self) -> bool {
+ self.expand || self.alignment != Alignment::Origin
+ }
}
/// Where to put content.
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index b2d07382..cdc35580 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -8,28 +8,31 @@ pub struct StackLayouter {
space: usize,
hard: bool,
- start: Size2D,
actions: LayoutActionList,
- combined_dimensions: Size2D,
+ combined_dimensions: Size2D, // <- specialized
sub: Subspace,
}
#[derive(Debug, Clone)]
struct Subspace {
+ origin: Size2D, // <- specialized
usable: Size2D,
- anchor: Size2D,
+ anchor: Size2D, // <- generic
factor: i32,
- dimensions: Size2D,
+ dimensions: Size2D, // <- generic
+ space: Option<Size>,
}
impl Subspace {
- fn new(usable: Size2D, axes: LayoutAxes) -> Subspace {
+ fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace {
Subspace {
- usable,
+ origin,
+ usable: axes.generalize(usable),
anchor: axes.anchor(usable),
factor: axes.secondary.axis.factor(),
dimensions: Size2D::zero(),
+ space: None,
}
}
}
@@ -41,7 +44,7 @@ impl Subspace {
pub struct StackContext {
pub spaces: LayoutSpaces,
pub axes: LayoutAxes,
- pub shrink_to_fit: bool,
+ pub expand: bool,
}
impl StackLayouter {
@@ -49,7 +52,6 @@ impl StackLayouter {
pub fn new(ctx: StackContext) -> StackLayouter {
let axes = ctx.axes;
let space = ctx.spaces[0];
- let usable = ctx.axes.generalize(space.usable());
StackLayouter {
ctx,
@@ -57,15 +59,16 @@ impl StackLayouter {
space: 0,
hard: true,
- start: space.start(),
actions: LayoutActionList::new(),
combined_dimensions: Size2D::zero(),
- sub: Subspace::new(usable, axes),
+ sub: Subspace::new(space.start(), space.usable(), axes),
}
}
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
+ self.layout_space();
+
let size = self.ctx.axes.generalize(layout.dimensions);
let mut new_dimensions = merge(self.sub.dimensions, size);
@@ -81,10 +84,9 @@ impl StackLayouter {
let offset = self.sub.dimensions.y;
let anchor = self.ctx.axes.anchor(size);
- let pos = self.ctx.axes.specialize(
- self.start
- + (self.sub.anchor - anchor)
- + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset)
+ let pos = self.sub.origin + self.ctx.axes.specialize(
+ (self.sub.anchor - anchor)
+ + Size2D::with_y(self.combined_dimensions.y + self.sub.factor * offset)
);
self.actions.add_layout(pos, layout);
@@ -100,19 +102,16 @@ impl StackLayouter {
Ok(())
}
- pub fn add_space(&mut self, space: Size) {
- if self.sub.dimensions.y + space > self.sub.usable.y {
- self.finish_space(false);
- } else {
- self.sub.dimensions.y += space;
+ pub fn add_space(&mut self, space: Size, soft: bool) {
+ self.sub.space = Some(space);
+ if !soft {
+ self.layout_space();
}
}
pub fn set_axes(&mut self, axes: LayoutAxes) {
if axes != self.ctx.axes {
- self.finish_subspace();
- self.ctx.axes = axes;
- self.sub = Subspace::new(self.remaining_subspace(), axes);
+ self.finish_subspace(axes);
}
}
@@ -128,7 +127,7 @@ impl StackLayouter {
pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = smallvec![LayoutSpace {
- dimensions: self.ctx.axes.specialize(self.remaining_subspace()),
+ dimensions: self.remaining_subspace().1,
padding: SizeBox::zero(),
}];
@@ -161,13 +160,13 @@ impl StackLayouter {
}
pub fn finish_space(&mut self, hard: bool) {
- self.finish_subspace();
+ self.finish_subspace(self.ctx.axes);
let space = self.ctx.spaces[self.space];
let actions = std::mem::replace(&mut self.actions, LayoutActionList::new());
self.layouts.add(Layout {
- dimensions: match self.ctx.shrink_to_fit {
+ dimensions: match self.ctx.expand {
true => self.combined_dimensions.padded(space.padding),
false => space.dimensions,
},
@@ -180,27 +179,60 @@ impl StackLayouter {
fn start_space(&mut self, space: usize, hard: bool) {
self.space = space;
-
let space = self.ctx.spaces[space];
- let usable = self.ctx.axes.generalize(space.usable());
self.hard = hard;
- self.start = space.start();
self.combined_dimensions = Size2D::zero();
- self.sub = Subspace::new(usable, self.ctx.axes);
+ self.sub = Subspace::new(space.start(), space.usable(), self.ctx.axes);
}
fn next_space(&self) -> usize {
(self.space + 1).min(self.ctx.spaces.len() - 1)
}
- fn finish_subspace(&mut self) {
- let dims = self.ctx.axes.specialize(self.sub.dimensions);
- self.combined_dimensions = merge(self.combined_dimensions, dims);
+ fn finish_subspace(&mut self, new_axes: LayoutAxes) {
+ if self.ctx.axes.primary.needs_expansion() {
+ self.sub.dimensions.x = self.sub.usable.x;
+ }
+
+ if self.ctx.axes.secondary.needs_expansion() {
+ self.sub.dimensions.y = self.sub.usable.y;
+ }
+
+ let (new_origin, new_usable) = self.remaining_subspace();
+
+ let origin = self.sub.origin;
+ let dimensions = self.ctx.axes.specialize(self.sub.dimensions);
+ let space = self.ctx.spaces[self.space];
+ self.combined_dimensions.max_eq(origin - space.start() + dimensions);
+
+ self.ctx.axes = new_axes;
+ self.sub = Subspace::new(new_origin, new_usable, new_axes);
}
- fn remaining_subspace(&self) -> Size2D {
- Size2D::new(self.sub.usable.x, self.sub.usable.y - self.sub.dimensions.y)
+ fn remaining_subspace(&self) -> (Size2D, Size2D) {
+ let used = self.ctx.axes.specialize(self.sub.usable);
+ let dimensions = self.ctx.axes.specialize(self.sub.dimensions);
+
+ let new_usable = self.ctx.axes.specialize(Size2D {
+ x: self.sub.usable.x,
+ y: self.sub.usable.y - self.sub.dimensions.y,
+ });
+
+ let new_origin = self.sub.origin
+ + Size2D::with_y(self.ctx.axes.specialize(self.sub.dimensions).y);
+
+ (new_origin, new_usable)
+ }
+
+ fn layout_space(&mut self) {
+ if let Some(space) = self.sub.space.take() {
+ if self.sub.dimensions.y + space > self.sub.usable.y {
+ self.finish_space(false);
+ } else {
+ self.sub.dimensions.y += space;
+ }
+ }
}
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index f7526500..d9f15618 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -23,7 +23,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
flex_spacing: flex_spacing(&ctx.text_style),
spaces: ctx.spaces.clone(),
axes: ctx.axes,
- shrink_to_fit: ctx.shrink_to_fit,
+ expand: ctx.expand,
}),
style: ctx.text_style.clone(),
ctx,
@@ -66,24 +66,25 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
- let (flex_spaces, stack_spaces) = self.flex.remaining()?;
+ let (first, second) = self.flex.remaining()?;
let ctx = |spaces| LayoutContext {
+ loader: self.ctx.loader,
top_level: false,
text_style: &self.style,
+ page_style: self.ctx.page_style,
spaces,
- shrink_to_fit: true,
- .. self.ctx
+ axes: self.ctx.axes.expanding(false),
+ expand: false,
};
- // Try putting it in the flex space first, but if that is not enough
- // space, use the other space.
- let commands = match func.body.val.layout(ctx(flex_spaces)) {
+ let commands = match func.body.val.layout(ctx(first)) {
Ok(c) => c,
- Err(LayoutError::NotEnoughSpace(_)) => {
- func.body.val.layout(ctx(stack_spaces))?
+ Err(e) => match (e, second) {
+ (LayoutError::NotEnoughSpace(_), Some(space))
+ => func.body.val.layout(ctx(space))?,
+ _ => Err(e)?,
},
- e => e?,
};
for command in commands {
@@ -101,10 +102,10 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, false),
- Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space)?,
+ Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, false)?,
Command::FinishLine => self.flex.add_break(),
- Command::FinishRun => self.flex.finish_run()?,
+ Command::FinishRun => { self.flex.finish_run()?; },
Command::FinishSpace => self.flex.finish_space(true)?,
Command::BreakParagraph => self.break_paragraph()?,
@@ -140,7 +141,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Finish the current flex layout and add space after it.
fn break_paragraph(&mut self) -> LayoutResult<()> {
- self.flex.add_secondary_space(paragraph_spacing(&self.style))
+ self.flex.add_secondary_space(paragraph_spacing(&self.style), true)
}
}