summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-11-15 19:55:47 +0100
committerLaurenz <laurmaedje@gmail.com>2019-11-15 19:55:47 +0100
commit0917d89bb899380ba897382b4945c8426f25c66d (patch)
treea9936019abcc7642657e14a828661c21f579dd70 /src/layout
parent9473ae61e9cfc419eb6b48fb0ed15441cc7ad48b (diff)
Generalize flex layouter 🎯
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/flex.rs154
-rw-r--r--src/layout/stacked.rs60
-rw-r--r--src/layout/tree.rs7
3 files changed, 94 insertions, 127 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index da1e1d59..a364b608 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -21,58 +21,31 @@ use super::*;
pub struct FlexLayouter {
ctx: FlexContext,
units: Vec<FlexUnit>,
-
stack: StackLayouter,
- usable_width: Size,
+
+ usable: Size,
run: FlexRun,
- cached_glue: Option<Size2D>,
+ space: Option<Size>,
}
/// The context for flex layouting.
///
/// See [`LayoutContext`] for details about the fields.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
pub struct FlexContext {
+ pub spaces: LayoutSpaces,
+ pub axes: LayoutAxes,
/// The spacing between two lines of boxes.
pub flex_spacing: Size,
- pub alignment: Alignment,
- pub space: LayoutSpace,
- pub followup_spaces: Option<LayoutSpace>,
- pub shrink_to_fit: bool,
-}
-
-macro_rules! reuse {
- ($ctx:expr, $flex_spacing:expr) => (
- FlexContext {
- flex_spacing: $flex_spacing,
- alignment: $ctx.alignment,
- space: $ctx.space,
- followup_spaces: $ctx.followup_spaces,
- shrink_to_fit: $ctx.shrink_to_fit,
- }
- );
-}
-
-impl FlexContext {
- /// Create a flex context from a generic layout context.
- pub fn from_layout_ctx(ctx: LayoutContext, flex_spacing: Size) -> FlexContext {
- reuse!(ctx, flex_spacing)
- }
-
- /// Create a flex context from a stack context.
- pub fn from_stack_ctx(ctx: StackContext, flex_spacing: Size) -> FlexContext {
- reuse!(ctx, flex_spacing)
- }
}
#[derive(Debug, Clone)]
enum FlexUnit {
/// A content unit to be arranged flexibly.
Boxed(Layout),
- /// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
- /// is only present if there was no flow break in between the two
- /// surrounding boxes.
- Glue(Size2D),
+ /// Space between two box units which is only present if there
+ /// was no flow break in between the two surrounding units.
+ Space(Size),
/// A forced break of the current flex run.
Break,
}
@@ -86,18 +59,19 @@ struct FlexRun {
impl FlexLayouter {
/// Create a new flex layouter.
pub fn new(ctx: FlexContext) -> FlexLayouter {
+ let stack = StackLayouter::new(StackContext {
+ spaces: ctx.spaces,
+ axes: ctx.axes,
+ });
+
FlexLayouter {
ctx,
units: vec![],
+ stack,
- stack: StackLayouter::new(StackContext::from_flex_ctx(ctx, Flow::Vertical)),
-
- usable_width: ctx.space.usable().x,
- run: FlexRun {
- content: vec![],
- size: Size2D::zero()
- },
- cached_glue: None,
+ usable: stack.usable().x,
+ run: FlexRun { content: vec![], size: Size2D::zero() },
+ space: None,
}
}
@@ -111,12 +85,12 @@ impl FlexLayouter {
self.units.push(FlexUnit::Boxed(layout));
}
- /// Add a glue box which can be replaced by a line break.
- pub fn add_glue(&mut self, glue: Size2D) {
- self.units.push(FlexUnit::Glue(glue));
+ /// Add a space box which can be replaced by a run break.
+ pub fn add_space(&mut self, space: Size) {
+ self.units.push(FlexUnit::Space(space));
}
- /// Add a forced line break.
+ /// Add a forced run break.
pub fn add_break(&mut self) {
self.units.push(FlexUnit::Break);
}
@@ -133,70 +107,71 @@ impl FlexLayouter {
for unit in units {
match unit {
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
- FlexUnit::Glue(glue) => self.layout_glue(glue),
- FlexUnit::Break => self.layout_break()?,
+ FlexUnit::Space(space) => {
+ self.space = Some(space);
+ }
+
+ FlexUnit::Break => {
+ self.space = None;
+ self.finish_run()?;
+ },
}
}
// Finish the last flex run.
self.finish_run()?;
- self.stack.finish()
+ Ok(self.stack.finish())
}
/// Layout a content box into the current flex run or start a new run if
/// it does not fit.
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
- let glue_width = self.cached_glue.unwrap_or(Size2D::zero()).x;
- let new_line_width = self.run.size.x + glue_width + boxed.dimensions.x;
-
- if self.overflows_line(new_line_width) {
- self.cached_glue = None;
-
- // If the box does not even fit on its own line, then we try
- // it in the next space, or we have to give up if there is none.
- if self.overflows_line(boxed.dimensions.x) {
- if self.ctx.followup_spaces.is_some() {
- self.stack.finish_layout(true)?;
- return self.layout_box(boxed);
- } else {
- return Err(LayoutError::NotEnoughSpace("cannot fit box into flex run"));
+ let size = boxed.dimensions.generalized(self.ctx.axes);
+
+ let space = self.space.unwrap_or(Size::zero());
+ let new_run_size = self.run.size.x + space + size.x;
+
+ if new_run_size > self.usable {
+ self.space = None;
+
+ while size.x > self.usable {
+ if self.stack.in_last_space() {
+ Err(LayoutError::NotEnoughSpace("cannot fix box into flex run"))?;
}
+
+ self.stack.finish_layout(true);
+ self.usable = self.stack.usable().x;
}
self.finish_run()?;
}
- self.flush_glue();
+ 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;
+ }
+ }
- let dimensions = boxed.dimensions;
self.run.content.push((self.run.size.x, boxed));
- self.grow_run(dimensions);
+ self.run.size.x += size.x;
+ self.run.size.y = crate::size::max(self.run.size.y, size.y);
Ok(())
}
- fn layout_glue(&mut self, glue: Size2D) {
- self.cached_glue = Some(glue);
- }
-
- fn layout_break(&mut self) -> LayoutResult<()> {
- self.cached_glue = None;
- self.finish_run()
- }
-
/// Finish the current flex run.
fn finish_run(&mut self) -> LayoutResult<()> {
- self.run.size.y += self.ctx.flex_spacing;
-
let mut actions = LayoutActionList::new();
for (x, layout) in self.run.content.drain(..) {
- let position = Size2D::with_x(x);
+ let position = Size2D::with_x(x).specialized(self.ctx.axes);
actions.add_layout(position, layout);
}
+ self.run.size.y += self.ctx.flex_spacing;
+
self.stack.add(Layout {
- dimensions: self.run.size,
+ dimensions: self.run.size.specialized(self.ctx.axes),
actions: actions.into_vec(),
debug_render: false,
})?;
@@ -206,25 +181,8 @@ impl FlexLayouter {
Ok(())
}
- fn flush_glue(&mut self) {
- if let Some(glue) = self.cached_glue.take() {
- if self.run.size.x > Size::zero() && !self.overflows_line(self.run.size.x + glue.x) {
- self.grow_run(glue);
- }
- }
- }
-
- fn grow_run(&mut self, dimensions: Size2D) {
- self.run.size.x += dimensions.x;
- self.run.size.y = crate::size::max(self.run.size.y, dimensions.y);
- }
-
/// Whether this layouter contains any items.
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
-
- fn overflows_line(&self, line: Size) -> bool {
- line > self.usable_width
- }
}
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index fd21c65c..419ec6b7 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -41,11 +41,6 @@ impl StackLayouter {
}
}
- /// This layouter's context.
- pub fn ctx(&self) -> StackContext {
- self.ctx
- }
-
/// Add a sublayout.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
let size = layout.dimensions.generalized(self.ctx.axes);
@@ -53,12 +48,11 @@ impl StackLayouter {
// Search for a suitable space to insert the box.
while !self.usable.fits(new_dimensions) {
- if self.active_space == self.ctx.spaces.len() - 1 {
- return Err(LayoutError::NotEnoughSpace("box is to large for stack spaces"));
+ if self.in_last_space() {
+ Err(LayoutError::NotEnoughSpace("cannot fit box into stack"))?;
}
- self.finish_layout()?;
- self.start_new_space(true);
+ self.finish_layout(true);
new_dimensions = self.size_with(size);
}
@@ -80,33 +74,36 @@ impl StackLayouter {
}
/// Add space after the last layout.
- pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
+ pub fn add_space(&mut self, space: Size) {
if self.dimensions.y + space > self.usable.y {
- self.finish_layout()?;
- self.start_new_space(false);
+ self.finish_layout(false);
} else {
self.dimensions.y += space;
}
-
- Ok(())
}
/// 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) -> LayoutResult<MultiLayout> {
+ pub fn finish(&mut self) -> MultiLayout {
if self.include_empty || !self.boxes.is_empty() {
- self.finish_layout()?;
+ self.finish_boxes();
}
- Ok(std::mem::replace(&mut self.layouts, MultiLayout::new()))
+ std::mem::replace(&mut self.layouts, MultiLayout::new())
}
/// Finish the current layout and start a new one in a new space.
///
- /// If `start_new_empty` is true, a new empty layout will be started. Otherwise,
- /// the new layout only appears once new content is added.
- pub fn finish_layout(&mut self) -> LayoutResult<()> {
+ /// If `include_empty` is true, the followup layout will even be
+ /// part of the finished multi-layout if it would be empty.
+ pub fn finish_layout(&mut self, include_empty: bool) {
+ self.finish_boxes();
+ self.start_new_space(include_empty);
+ }
+
+ /// Compose all cached boxes into a layout.
+ fn finish_boxes(&mut self) {
let mut actions = LayoutActionList::new();
let space = self.ctx.spaces[self.active_space];
@@ -116,7 +113,7 @@ impl StackLayouter {
for (offset, layout_anchor, layout) in self.boxes.drain(..) {
let general_position = anchor - layout_anchor + Size2D::with_y(offset * factor);
- let position = general_position.specialized(self.ctx.axes) + start;
+ let position = start + general_position.specialized(self.ctx.axes);
actions.add_layout(position, layout);
}
@@ -130,8 +127,6 @@ impl StackLayouter {
actions: actions.into_vec(),
debug_render: true,
});
-
- Ok(())
}
/// Set up layouting in the next space. Should be preceded by `finish_layout`.
@@ -139,19 +134,34 @@ impl StackLayouter {
/// If `include_empty` is true, the new empty layout will always be added when
/// finishing this stack. Otherwise, the new layout only appears if new
/// content is added to it.
- pub fn start_new_space(&mut self, include_empty: bool) {
+ fn start_new_space(&mut self, include_empty: bool) {
self.active_space = (self.active_space + 1).min(self.ctx.spaces.len() - 1);
self.usable = self.ctx.spaces[self.active_space].usable().generalized(self.ctx.axes);
self.dimensions = start_dimensions(self.usable, self.ctx.axes);
self.include_empty = include_empty;
}
- /// The remaining space for new layouts.
+ /// This layouter's context.
+ pub fn ctx(&self) -> StackContext {
+ self.ctx
+ }
+
+ /// The (generalized) usable area of the current space.
+ pub fn usable(&self) -> Size2D {
+ self.usable
+ }
+
+ /// The (specialized) remaining area for new layouts in the current space.
pub fn remaining(&self) -> Size2D {
Size2D::new(self.usable.x, self.usable.y - self.dimensions.y)
.specialized(self.ctx.axes)
}
+ /// Whether this layouter is in its last space.
+ pub fn in_last_space(&self) -> bool {
+ self.active_space == self.ctx.spaces.len() - 1
+ }
+
/// The combined size of the so-far included boxes with the other size.
fn size_with(&self, other: Size2D) -> Size2D {
Size2D {
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 4f86f721..b64dd6eb 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -59,7 +59,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
if self.set_newline {
let space = paragraph_spacing(&self.style);
- self.stack.add_space(space)?;
+ self.stack.add_space(space);
self.set_newline = false;
}
@@ -81,7 +81,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Finish the layout.
fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_flex()?;
- self.stack.finish()
+ Ok(self.stack.finish())
}
/// Layout a function.
@@ -136,8 +136,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::FinishLayout => {
self.finish_flex()?;
- self.stack.finish_layout()?;
- self.stack.start_new_space(true);
+ self.stack.finish_layout(true);
self.start_new_flex();
}