diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-06-21 21:37:29 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-06-21 21:41:02 +0200 |
| commit | 968e121697a96a2e3b05a560176c34f4bb6693c3 (patch) | |
| tree | b937cf208d7a8bfb318227a46e44f91da4ef7a49 /src/layout/flex.rs | |
| parent | b53ad6b1ec8b2fd05566a83c9b895f265e61d281 (diff) | |
Implement flex and box layouting 📏
Diffstat (limited to 'src/layout/flex.rs')
| -rw-r--r-- | src/layout/flex.rs | 168 |
1 files changed, 139 insertions, 29 deletions
diff --git a/src/layout/flex.rs b/src/layout/flex.rs index faddc95a..924ebec5 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -1,61 +1,171 @@ //! Flexible and lazy layouting of boxes. -use super::{Layouter, LayoutContext, BoxLayout}; +use crate::doc::TextAction; +use crate::size::Size2D; +use super::{LayoutSpace, BoxLayout}; /// A flex layout consists of a yet unarranged list of boxes. #[derive(Debug, Clone)] pub struct FlexLayout { /// The sublayouts composing this layout. - layouts: Vec<BoxLayout>, + pub units: Vec<FlexUnit>, + /// The layout space to arrange in. + pub ctx: FlexContext, } -impl FlexLayout { - /// Compute the layout. - pub fn into_box(self) -> BoxLayout { - // TODO: Do the justification. - unimplemented!() - } -} - -/// Layouts boxes next to each other (inline-style) lazily. -#[derive(Debug)] -pub struct FlexLayouter<'a, 'p> { - ctx: &'a LayoutContext<'a, 'p>, - layouts: Vec<BoxLayout>, +/// A unit in a flex layout. +#[derive(Debug, Clone)] +pub enum FlexUnit { + /// A content unit to be arranged flexibly. + Boxed(BoxLayout), + /// 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(BoxLayout), } -impl<'a, 'p> FlexLayouter<'a, 'p> { +impl FlexLayout { /// Create a new flex layouter. - pub fn new(ctx: &'a LayoutContext<'a, 'p>) -> FlexLayouter<'a, 'p> { - FlexLayouter { + pub fn new(ctx: FlexContext) -> FlexLayout { + FlexLayout { ctx, - layouts: vec![], + units: vec![], } } /// Add a sublayout. pub fn add_box(&mut self, layout: BoxLayout) { - self.layouts.push(layout); + self.units.push(FlexUnit::Boxed(layout)); + } + + /// Add a glue layout which can be replaced by a line break. + pub fn add_glue(&mut self, glue: BoxLayout) { + self.units.push(FlexUnit::Glue(glue)); } /// Add all sublayouts of another flex layout. pub fn add_flexible(&mut self, layout: FlexLayout) { - self.layouts.extend(layout.layouts); + self.units.extend(layout.units); + } + + /// Whether this layouter contains any items. + pub fn is_empty(&self) -> bool { + self.units.is_empty() + } + + /// Compute the justified layout. + pub fn into_box(self) -> BoxLayout { + FlexFinisher::new(self).finish() } } -impl Layouter for FlexLayouter<'_, '_> { - type Layout = FlexLayout; +/// The context for flex layouting. +#[derive(Debug, Copy, Clone)] +pub struct FlexContext { + /// The space to layout the boxes in. + pub space: LayoutSpace, + /// The flex spacing (like line spacing). + pub flex_spacing: f32, +} - /// Finish the layouting and create a flexible layout from this. - fn finish(self) -> FlexLayout { - FlexLayout { - layouts: self.layouts +/// Finishes a flex layout by justifying the positions of the individual boxes. +#[derive(Debug)] +struct FlexFinisher { + units: Vec<FlexUnit>, + ctx: FlexContext, + actions: Vec<TextAction>, + dimensions: Size2D, + usable: Size2D, + cursor: Size2D, + line: Size2D, +} + +impl FlexFinisher { + /// Create the finisher from the layout. + fn new(layout: FlexLayout) -> FlexFinisher { + let space = layout.ctx.space; + FlexFinisher { + units: layout.units, + ctx: layout.ctx, + actions: vec![], + dimensions: Size2D::zero(), + usable: space.usable(), + cursor: Size2D::new(space.padding.left, space.padding.top), + line: Size2D::zero(), + } + } + + /// Finish the flex layout into the justified box layout. + fn finish(mut self) -> BoxLayout { + // Move the units out of the layout. + let units = self.units; + self.units = vec![]; + + // Arrange the units. + for unit in units { + match unit { + FlexUnit::Boxed(boxed) => self.boxed(boxed), + FlexUnit::Glue(glue) => self.glue(glue), + } } + + // Flush everything to get the correct dimensions. + self.newline(); + + BoxLayout { + dimensions: if self.ctx.space.shrink_to_fit { + self.dimensions.padded(self.ctx.space.padding) + } else { + self.ctx.space.dimensions + }, + actions: self.actions, + } + } + + /// Layout the box. + fn boxed(&mut self, boxed: BoxLayout) { + // Move to the next line if necessary. + if self.line.x + boxed.dimensions.x > self.usable.x { + // If it still does not fit, we stand no chance. + if boxed.dimensions.x > self.usable.x { + panic!("flex layouter: box is to wide"); + } + + self.newline(); + } + + self.append(boxed); + } + + /// Layout the glue. + fn glue(&mut self, glue: BoxLayout) { + // Only add the glue if it fits on the line, otherwise move to the next line. + if self.line.x + glue.dimensions.x > self.usable.x { + self.newline(); + } else { + self.append(glue); + } + } + + /// Append a box to the layout without checking anything. + fn append(&mut self, layout: BoxLayout) { + // Move all actions into this layout and translate absolute positions. + self.actions.push(TextAction::MoveAbsolute(self.cursor)); + self.actions.extend(super::translate_actions(self.cursor, layout.actions)); + + // Adjust the sizes. + self.line.x += layout.dimensions.x; + self.line.y = crate::size::max(self.line.y, layout.dimensions.y); + self.cursor.x += layout.dimensions.x; } - fn is_empty(&self) -> bool { - self.layouts.is_empty() + /// Move to the next line. + fn newline(&mut self) { + self.line.y *= self.ctx.flex_spacing; + self.dimensions.x = crate::size::max(self.dimensions.x, self.line.x); + self.dimensions.y += self.line.y; + self.cursor.x = self.ctx.space.padding.left; + self.cursor.y += self.line.y; + self.line = Size2D::zero(); } } |
