First half of flex refactoring 🥝

This commit is contained in:
Laurenz 2019-11-19 23:27:56 +01:00
parent 6afc84cb9d
commit 1dafe2c2ea
6 changed files with 114 additions and 142 deletions

View File

@ -97,9 +97,9 @@ pub enum Command<'a> {
AddPrimarySpace(Size), AddPrimarySpace(Size),
AddSecondarySpace(Size), AddSecondarySpace(Size),
FinishLine,
FinishRun, FinishRun,
FinishBox, FinishSpace,
FinishLayout,
BreakParagraph, BreakParagraph,

View File

@ -1,40 +1,60 @@
use super::*; use super::*;
/// Layouts boxes flex-like.
///
/// The boxes are arranged in "lines", each line having the height of its
/// biggest box. When a box does not fit on a line anymore horizontally,
/// a new line is started.
///
/// The flex layouter does not actually compute anything until the `finish`
/// method is called. The reason for this is the flex layouter will have
/// the capability to justify its layouts, later. To find a good justification
/// it needs total information about the contents.
///
/// There are two different kinds units that can be added to a flex run:
/// Normal layouts and _glue_. _Glue_ layouts are only written if a normal
/// layout follows and a glue layout is omitted if the following layout
/// flows into a new line. A _glue_ layout is typically used for a space character
/// since it prevents a space from appearing in the beginning or end of a line.
/// However, it can be any layout.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FlexLayouter { pub struct FlexLayouter {
stack: StackLayouter,
axes: LayoutAxes, axes: LayoutAxes,
flex_spacing: Size, flex_spacing: Size,
stack: StackLayouter,
units: Vec<FlexUnit>, units: Vec<FlexUnit>,
line: FlexLine,
}
total_usable: Size, #[derive(Debug, Clone)]
merged_actions: LayoutActionList, enum FlexUnit {
merged_dimensions: Size2D, Boxed(Layout),
max_extent: Size, Space(Size, bool),
SetAxes(LayoutAxes),
Break,
}
#[derive(Debug, Clone)]
struct FlexLine {
usable: Size, usable: Size,
run: FlexRun, actions: LayoutActionList,
space: Option<Size>, combined_dimensions: Size2D,
part: PartialLine,
}
last_run_remaining: Size2D, impl FlexLine {
fn new(usable: Size) -> FlexLine {
FlexLine {
usable,
actions: LayoutActionList::new(),
combined_dimensions: Size2D::zero(),
part: PartialLine::new(usable),
}
}
}
#[derive(Debug, Clone)]
struct PartialLine {
usable: Size,
content: Vec<(Size, Layout)>,
dimensions: Size2D,
space: Option<(Size, bool)>,
}
impl PartialLine {
fn new(usable: Size) -> PartialLine {
PartialLine {
usable,
content: vec![],
dimensions: Size2D::zero(),
space: None,
}
}
} }
/// The context for flex layouting. /// The context for flex layouting.
@ -45,28 +65,9 @@ pub struct FlexContext {
pub spaces: LayoutSpaces, pub spaces: LayoutSpaces,
pub axes: LayoutAxes, pub axes: LayoutAxes,
pub shrink_to_fit: bool, pub shrink_to_fit: bool,
/// The spacing between two lines of boxes.
pub flex_spacing: Size, pub flex_spacing: Size,
} }
#[derive(Debug, Clone)]
enum FlexUnit {
/// A content unit to be arranged flexibly.
Boxed(Layout),
/// 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,
SetAxes(LayoutAxes),
}
#[derive(Debug, Clone)]
struct FlexRun {
content: Vec<(Size, Layout)>,
size: Size2D,
}
impl FlexLayouter { impl FlexLayouter {
/// Create a new flex layouter. /// Create a new flex layouter.
pub fn new(ctx: FlexContext) -> FlexLayouter { pub fn new(ctx: FlexContext) -> FlexLayouter {
@ -78,132 +79,126 @@ impl FlexLayouter {
let usable = stack.primary_usable(); let usable = stack.primary_usable();
FlexLayouter { FlexLayouter {
stack,
axes: ctx.axes, axes: ctx.axes,
flex_spacing: ctx.flex_spacing, flex_spacing: ctx.flex_spacing,
units: vec![], units: vec![],
stack, line: FlexLine::new(usable)
total_usable: usable,
merged_actions: LayoutActionList::new(),
merged_dimensions: Size2D::zero(),
max_extent: Size::zero(),
usable,
run: FlexRun { content: vec![], size: Size2D::zero() },
space: None,
last_run_remaining: Size2D::zero(),
} }
} }
/// Add a sublayout.
pub fn add(&mut self, layout: Layout) { pub fn add(&mut self, layout: Layout) {
self.units.push(FlexUnit::Boxed(layout)); self.units.push(FlexUnit::Boxed(layout));
} }
/// Add multiple sublayouts from a multi-layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) { pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts { for layout in layouts {
self.add(layout); self.add(layout);
} }
} }
/// Add a forced run break. pub fn add_break(&mut self) {
pub fn add_run_break(&mut self) {
self.units.push(FlexUnit::Break); self.units.push(FlexUnit::Break);
} }
/// Add a space box which can be replaced by a run break. pub fn add_primary_space(&mut self, space: Size, soft: bool) {
pub fn add_primary_space(&mut self, space: Size) { self.units.push(FlexUnit::Space(space, soft));
self.units.push(FlexUnit::Space(space));
} }
pub fn add_secondary_space(&mut self, space: Size) -> LayoutResult<()> { pub fn add_secondary_space(&mut self, space: Size) -> LayoutResult<()> {
self.finish_box()?; self.finish_run()?;
self.stack.add_space(space); Ok(self.stack.add_space(space))
Ok(())
} }
/// Update the axes in use by this flex layouter.
pub fn set_axes(&mut self, axes: LayoutAxes) { pub fn set_axes(&mut self, axes: LayoutAxes) {
self.units.push(FlexUnit::SetAxes(axes)); self.units.push(FlexUnit::SetAxes(axes));
} }
/// Update the followup space to be used by this flex layouter.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.box_is_empty() && self.stack.space_is_empty() { if replace_empty && self.run_is_empty() && self.stack.space_is_empty() {
self.stack.set_spaces(spaces, true); self.stack.set_spaces(spaces, true);
self.total_usable = self.stack.primary_usable(); self.start_run();
self.usable = self.total_usable;
self.space = None; // 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 { } else {
self.stack.set_spaces(spaces, false); self.stack.set_spaces(spaces, false);
} }
} }
/// Compute the justified layout. pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
/// let mut future = self.clone();
/// The layouter is not consumed by this to prevent ownership problems future.finish_run()?;
/// with borrowed layouters. The state of the layouter is not reset.
/// Therefore, it should not be further used after calling `finish`. 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, stack_spaces))
}
pub fn run_is_empty(&self) -> bool {
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
}
pub fn run_last_is_space(&self) -> bool {
matches!(self.units.last(), Some(FlexUnit::Space(_)))
}
pub fn finish(mut self) -> LayoutResult<MultiLayout> { pub fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_box()?; self.finish_space(false)?;
Ok(self.stack.finish()) Ok(self.stack.finish())
} }
pub fn finish_layout(&mut self, hard: bool) -> LayoutResult<()> { pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
self.finish_box()?; self.finish_run()?;
self.stack.finish_layout(hard); Ok(self.stack.finish_space(hard))
Ok(())
} }
pub fn finish_box(&mut self) -> LayoutResult<()> { pub fn finish_run(&mut self) -> LayoutResult<()> {
if self.box_is_empty() {
return Ok(());
}
// Move the units out of the layout because otherwise, we run into
// ownership problems.
let units = std::mem::replace(&mut self.units, vec![]); let units = std::mem::replace(&mut self.units, vec![]);
for unit in units { for unit in units {
match unit { match unit {
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
FlexUnit::Space(space) => { FlexUnit::Space(space, soft) => {
self.layout_space(); self.layout_space();
self.space = Some(space); self.space = Some(space);
} }
FlexUnit::Break => { FlexUnit::Break => {
self.space = None; self.space = None;
self.finish_run()?; self.finish_line()?;
}, },
FlexUnit::SetAxes(axes) => self.layout_set_axes(axes), FlexUnit::SetAxes(axes) => self.layout_set_axes(axes),
} }
} }
// Finish the last flex run. self.finish_line()?;
self.finish_run()?;
Ok(()) Ok(())
} }
/// 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<()> { fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
let size = self.axes.generalize(boxed.dimensions); let size = self.axes.generalize(boxed.dimensions);
if size.x > self.size_left() { if size.x > self.size_left() {
self.space = None; self.space = None;
self.finish_run()?; self.finish_line()?;
while size.x > self.usable { while size.x > self.usable {
if self.stack.in_last_space() { if self.stack.space_is_last() {
Err(LayoutError::NotEnoughSpace("cannot fix box into flex run"))?; Err(LayoutError::NotEnoughSpace("cannot fix box into flex run"))?;
} }
self.stack.finish_layout(true); self.finish_space(true);
self.total_usable = self.stack.primary_usable(); self.total_usable = self.stack.primary_usable();
self.usable = self.total_usable; self.usable = self.total_usable;
} }
@ -230,7 +225,7 @@ impl FlexLayouter {
fn layout_set_axes(&mut self, axes: LayoutAxes) { fn layout_set_axes(&mut self, axes: LayoutAxes) {
if axes.primary != self.axes.primary { if axes.primary != self.axes.primary {
self.finish_aligned_run(); self.finish_partial_line();
self.usable = match axes.primary.alignment { self.usable = match axes.primary.alignment {
Alignment::Origin => Alignment::Origin =>
@ -254,9 +249,8 @@ impl FlexLayouter {
self.axes = axes; self.axes = axes;
} }
/// Finish the current flex run. fn finish_line(&mut self) -> LayoutResult<()> {
fn finish_run(&mut self) -> LayoutResult<()> { self.finish_partial_line();
self.finish_aligned_run();
if self.merged_dimensions.y == Size::zero() { if self.merged_dimensions.y == Size::zero() {
return Ok(()); return Ok(());
@ -276,7 +270,7 @@ impl FlexLayouter {
Ok(()) Ok(())
} }
fn finish_aligned_run(&mut self) { fn finish_partial_line(&mut self) {
if self.run.content.is_empty() { if self.run.content.is_empty() {
return; return;
} }
@ -308,27 +302,6 @@ impl FlexLayouter {
self.run.size = Size2D::zero(); self.run.size = Size2D::zero();
} }
pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, LayoutSpaces)> {
let mut future = self.clone();
future.finish_box()?;
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, stack_spaces))
}
pub fn box_is_empty(&self) -> bool {
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
}
pub fn last_is_space(&self) -> bool {
matches!(self.units.last(), Some(FlexUnit::Space(_)))
}
fn size_left(&self) -> Size { fn size_left(&self) -> Size {
let space = self.space.unwrap_or(Size::zero()); let space = self.space.unwrap_or(Size::zero());
self.usable - (self.run.size.x + space) self.usable - (self.run.size.x + space)

View File

@ -47,9 +47,9 @@ pub struct StackContext {
impl StackLayouter { impl StackLayouter {
/// Create a new stack layouter. /// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter { pub fn new(ctx: StackContext) -> StackLayouter {
let axes = ctx.axes;
let space = ctx.spaces[0]; let space = ctx.spaces[0];
let usable = ctx.axes.generalize(space.usable()); let usable = ctx.axes.generalize(space.usable());
let axes = ctx.axes;
StackLayouter { StackLayouter {
ctx, ctx,
@ -146,6 +146,7 @@ impl StackLayouter {
pub fn space_is_empty(&self) -> bool { pub fn space_is_empty(&self) -> bool {
self.combined_dimensions == Size2D::zero() self.combined_dimensions == Size2D::zero()
&& self.sub.dimensions == Size2D::zero() && self.sub.dimensions == Size2D::zero()
&& self.actions.is_empty()
} }
pub fn space_is_last(&self) -> bool { pub fn space_is_last(&self) -> bool {

View File

@ -42,13 +42,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
} }
Node::Space => { Node::Space => {
if !self.flex.box_is_empty() && !self.flex.last_is_space() { if !self.flex.run_is_empty() && !self.flex.run_last_is_space() {
let space = self.style.word_spacing * self.style.font_size; let space = self.style.word_spacing * self.style.font_size;
self.flex.add_primary_space(space); self.flex.add_primary_space(space, true);
} }
} }
Node::Newline => { Node::Newline => {
if !self.flex.box_is_empty() { if !self.flex.run_is_empty() {
self.break_paragraph()?; self.break_paragraph()?;
} }
} }
@ -71,7 +71,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let ctx = |spaces| LayoutContext { let ctx = |spaces| LayoutContext {
top_level: false, top_level: false,
text_style: &self.style, text_style: &self.style,
spaces: spaces, spaces,
shrink_to_fit: true, shrink_to_fit: true,
.. self.ctx .. self.ctx
}; };
@ -100,12 +100,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
Command::Add(layout) => self.flex.add(layout), Command::Add(layout) => self.flex.add(layout),
Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts),
Command::AddPrimarySpace(space) => self.flex.add_primary_space(space), 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)?,
Command::FinishRun => self.flex.add_run_break(), Command::FinishLine => self.flex.add_break(),
Command::FinishBox => self.flex.finish_box()?, Command::FinishRun => self.flex.finish_run()?,
Command::FinishLayout => self.flex.finish_layout(true)?, Command::FinishSpace => self.flex.finish_space(true)?,
Command::BreakParagraph => self.break_paragraph()?, Command::BreakParagraph => self.break_paragraph()?,
@ -140,9 +140,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Finish the current flex layout and add space after it. /// Finish the current flex layout and add space after it.
fn break_paragraph(&mut self) -> LayoutResult<()> { fn break_paragraph(&mut self) -> LayoutResult<()> {
self.flex.finish_box()?; self.flex.add_secondary_space(paragraph_spacing(&self.style))
self.flex.add_secondary_space(paragraph_spacing(&self.style))?;
Ok(())
} }
} }

View File

@ -7,7 +7,7 @@ pub struct PageBreak;
function! { function! {
data: PageBreak, data: PageBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![FinishLayout]) } layout(_, _) { Ok(commands![FinishSpace]) }
} }
/// `page.size`: Set the size of pages. /// `page.size`: Set the size of pages.

View File

@ -7,7 +7,7 @@ pub struct LineBreak;
function! { function! {
data: LineBreak, data: LineBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![FinishRun]) } layout(_, _) { Ok(commands![FinishLine]) }
} }
/// `paragraph.break`: Ends the current paragraph. /// `paragraph.break`: Ends the current paragraph.
@ -19,7 +19,7 @@ pub struct ParagraphBreak;
function! { function! {
data: ParagraphBreak, data: ParagraphBreak,
parse: plain, parse: plain,
layout(_, _) { Ok(commands![FinishBox]) } layout(_, _) { Ok(commands![FinishRun]) }
} }
macro_rules! space_func { macro_rules! space_func {