summaryrefslogtreecommitdiff
path: root/src/layout/flex.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-06-21 21:37:29 +0200
committerLaurenz <laurmaedje@gmail.com>2019-06-21 21:41:02 +0200
commit968e121697a96a2e3b05a560176c34f4bb6693c3 (patch)
treeb937cf208d7a8bfb318227a46e44f91da4ef7a49 /src/layout/flex.rs
parentb53ad6b1ec8b2fd05566a83c9b895f265e61d281 (diff)
Implement flex and box layouting 📏
Diffstat (limited to 'src/layout/flex.rs')
-rw-r--r--src/layout/flex.rs168
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();
}
}