summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/layout/actions.rs65
-rw-r--r--src/layout/flex.rs177
-rw-r--r--src/layout/mod.rs4
-rw-r--r--src/layout/stacked.rs76
-rw-r--r--src/size.rs7
5 files changed, 185 insertions, 144 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index bbefbfc0..67ad48b4 100644
--- a/src/layout/actions.rs
+++ b/src/layout/actions.rs
@@ -17,14 +17,13 @@ pub enum LayoutAction {
/// Write text starting at the current position.
WriteText(String),
/// Visualize a box for debugging purposes.
- /// Arguments are position and size.
+ /// The arguments are position and size.
DebugBox(Size2D, Size2D),
}
impl LayoutAction {
- /// Serialize this layout action into a string representation.
+ /// Serialize this layout action into an easy-to-parse string representation.
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
- use LayoutAction::*;
match self {
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
SetFont(i, s) => write!(f, "f {} {}", i, s),
@@ -55,7 +54,17 @@ impl Display for LayoutAction {
debug_display!(LayoutAction);
-/// Unifies and otimizes lists of actions.
+/// A sequence of layouting actions.
+///
+/// The sequence of actions is optimized as the actions are added. For example,
+/// a font changing option will only be added if the selected font is not already active.
+/// All configuration actions (like moving, setting fonts, ...) are only flushed when
+/// content is written.
+///
+/// Furthermore, the action list can translate absolute position into a coordinate system
+/// with a different. This is realized in the `add_box` method, which allows a layout to
+/// be added at a position, effectively translating all movement actions inside the layout
+/// by the position.
#[derive(Debug, Clone)]
pub struct LayoutActionList {
pub origin: Size2D,
@@ -77,8 +86,7 @@ impl LayoutActionList {
}
}
- /// Add an action to the list if it is not useless
- /// (like changing to a font that is already active).
+ /// Add an action to the list.
pub fn add(&mut self, action: LayoutAction) {
match action {
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
@@ -89,16 +97,8 @@ impl LayoutActionList {
}
_ => {
- if let Some(target) = self.next_pos.take() {
- self.actions.push(MoveAbsolute(target));
- }
-
- if let Some((index, size)) = self.next_font.take() {
- if (index, size) != self.active_font {
- self.actions.push(SetFont(index, size));
- self.active_font = (index, size);
- }
- }
+ self.flush_position();
+ self.flush_font();
self.actions.push(action);
}
@@ -113,20 +113,16 @@ impl LayoutActionList {
}
}
- /// Add all actions from a box layout at a position. A move to the position
- /// is generated and all moves inside the box layout are translated as
- /// necessary.
- pub fn add_box(&mut self, position: Size2D, layout: Layout) {
- if let Some(target) = self.next_pos.take() {
- self.actions.push(MoveAbsolute(target));
- }
+ /// Add a layout at a position. All move actions inside the layout are translated
+ /// by the position.
+ pub fn add_layout(&mut self, position: Size2D, layout: Layout) {
+ self.flush_position();
- self.next_pos = Some(position);
self.origin = position;
+ self.next_pos = Some(position);
if layout.debug_render {
- self.actions
- .push(LayoutAction::DebugBox(position, layout.dimensions));
+ self.actions.push(DebugBox(position, layout.dimensions));
}
self.extend(layout.actions);
@@ -141,4 +137,21 @@ impl LayoutActionList {
pub fn into_vec(self) -> Vec<LayoutAction> {
self.actions
}
+
+ /// Append a cached move action if one is cached.
+ fn flush_position(&mut self) {
+ if let Some(target) = self.next_pos.take() {
+ self.actions.push(MoveAbsolute(target));
+ }
+ }
+
+ /// Append a cached font-setting action if one is cached.
+ fn flush_font(&mut self) {
+ if let Some((index, size)) = self.next_font.take() {
+ if (index, size) != self.active_font {
+ self.actions.push(SetFont(index, size));
+ self.active_font = (index, size);
+ }
+ }
+ }
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index ab1f066e..33f3d38b 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -1,19 +1,33 @@
use super::*;
-/// Finishes a flex layout by justifying the positions of the individual boxes.
-#[derive(Debug)]
+/// Flex-layouting of boxes.
+///
+/// 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.
pub struct FlexLayouter {
ctx: FlexContext,
units: Vec<FlexUnit>,
actions: LayoutActionList,
- dimensions: Size2D,
usable: Size2D,
+ dimensions: Size2D,
cursor: Size2D,
- line_content: Vec<(Size2D, Layout)>,
- line_metrics: Size2D,
- last_glue: Option<Layout>,
+ run: FlexRun,
+ next_glue: Option<Layout>,
}
/// The context for flex layouting.
@@ -21,12 +35,10 @@ pub struct FlexLayouter {
pub struct FlexContext {
/// The space to layout the boxes in.
pub space: LayoutSpace,
- /// The flex spacing between two lines of boxes.
+ /// The spacing between two lines of boxes.
pub flex_spacing: Size,
}
-/// A unit in a flex layout.
-#[derive(Debug, Clone)]
enum FlexUnit {
/// A content unit to be arranged flexibly.
Boxed(Layout),
@@ -36,6 +48,11 @@ enum FlexUnit {
Glue(Layout),
}
+struct FlexRun {
+ content: Vec<(Size2D, Layout)>,
+ size: Size2D,
+}
+
impl FlexLayouter {
/// Create a new flex layouter.
pub fn new(ctx: FlexContext) -> FlexLayouter {
@@ -44,16 +61,16 @@ impl FlexLayouter {
units: vec![],
actions: LayoutActionList::new(),
+ usable: ctx.space.usable(),
dimensions: match ctx.space.alignment {
Alignment::Left => Size2D::zero(),
Alignment::Right => Size2D::with_x(ctx.space.usable().x),
},
- usable: ctx.space.usable(),
+
cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
- line_content: vec![],
- line_metrics: Size2D::zero(),
- last_glue: None,
+ run: FlexRun::new(),
+ next_glue: None,
}
}
@@ -72,27 +89,22 @@ impl FlexLayouter {
self.units.push(FlexUnit::Glue(glue));
}
- /// Whether this layouter contains any items.
- pub fn is_empty(&self) -> bool {
- self.units.is_empty()
- }
-
/// Compute the justified layout.
pub fn finish(mut self) -> LayoutResult<Layout> {
- // Move the units out of the layout.
+ // Move the units out of the layout because otherwise, we run into
+ // ownership problems.
let units = self.units;
- self.units = vec![];
+ self.units = Vec::new();
- // Arrange the units.
for unit in units {
match unit {
- FlexUnit::Boxed(boxed) => self.boxed(boxed)?,
- FlexUnit::Glue(glue) => self.glue(glue),
+ FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
+ FlexUnit::Glue(glue) => self.layout_glue(glue),
}
}
- // Flush everything to get the correct dimensions.
- self.newline();
+ // Finish the last flex run.
+ self.finish_flex_run();
Ok(Layout {
dimensions: if self.ctx.space.shrink_to_fit {
@@ -105,82 +117,95 @@ impl FlexLayouter {
})
}
- /// Layout the box.
- fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
- let last_glue_x = self
- .last_glue
+ /// Whether this layouter contains any items.
+ pub fn is_empty(&self) -> bool {
+ self.units.is_empty()
+ }
+
+ fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
+ let next_glue_width = self
+ .next_glue
.as_ref()
.map(|g| g.dimensions.x)
.unwrap_or(Size::zero());
- // Move to the next line if necessary.
- if self.line_metrics.x + boxed.dimensions.x + last_glue_x > self.usable.x {
- // If it still does not fit, we stand no chance.
- if boxed.dimensions.x > self.usable.x {
+ let new_line_width = self.run.size.x + next_glue_width + boxed.dimensions.x;
+
+ if self.overflows(new_line_width) {
+ // If the box does not even fit on its own line, then
+ // we can't do anything.
+ if self.overflows(boxed.dimensions.x) {
return Err(LayoutError::NotEnoughSpace);
}
- self.newline();
- } else if let Some(glue) = self.last_glue.take() {
- self.append(glue);
+ self.finish_flex_run();
+ } else {
+ // Only add the glue if we did not move to a new line.
+ self.flush_glue();
}
- self.append(boxed);
+ self.add_to_flex_run(boxed);
Ok(())
}
- /// Layout the glue.
- fn glue(&mut self, glue: Layout) {
- if let Some(glue) = self.last_glue.take() {
- self.append(glue);
+ fn layout_glue(&mut self, glue: Layout) {
+ self.flush_glue();
+ self.next_glue = Some(glue);
+ }
+
+ fn flush_glue(&mut self) {
+ if let Some(glue) = self.next_glue.take() {
+ self.add_to_flex_run(glue);
}
- self.last_glue = Some(glue);
}
- /// Append a box to the layout without checking anything.
- fn append(&mut self, layout: Layout) {
- let dim = layout.dimensions;
- self.line_content.push((self.cursor, layout));
+ fn add_to_flex_run(&mut self, layout: Layout) {
+ let position = self.cursor;
- self.line_metrics.x += dim.x;
- self.line_metrics.y = crate::size::max(self.line_metrics.y, dim.y);
- self.cursor.x += dim.x;
+ self.cursor.x += layout.dimensions.x;
+ self.run.size.x += layout.dimensions.x;
+ self.run.size.y = crate::size::max(self.run.size.y, layout.dimensions.y);
+
+ self.run.content.push((position, layout));
}
- /// Move to the next line.
- fn newline(&mut self) {
- // Move all actions into this layout and translate absolute positions.
- let remaining_space = Size2D::with_x(self.ctx.space.usable().x - self.line_metrics.x);
- for (cursor, layout) in self.line_content.drain(..) {
- let position = match self.ctx.space.alignment {
- Alignment::Left => cursor,
- Alignment::Right => {
- // Right align everything by shifting it right by the
- // amount of space left to the right of the line.
- cursor + remaining_space
+ fn finish_flex_run(&mut self) {
+ // Add all layouts from the current flex run at the correct positions.
+ match self.ctx.space.alignment {
+ Alignment::Left => {
+ for (position, layout) in self.run.content.drain(..) {
+ self.actions.add_layout(position, layout);
}
- };
-
- self.actions.add_box(position, layout);
- }
-
- // Stretch the dimensions to at least the line width.
- self.dimensions.x = crate::size::max(self.dimensions.x, self.line_metrics.x);
+ }
- // If we wrote a line previously add the inter-line spacing.
- if self.dimensions.y > Size::zero() {
- self.dimensions.y += self.ctx.flex_spacing;
+ Alignment::Right => {
+ let extra_space = Size2D::with_x(self.usable.x - self.run.size.x);
+ for (position, layout) in self.run.content.drain(..) {
+ self.actions.add_layout(position + extra_space, layout);
+ }
+ }
}
- self.dimensions.y += self.line_metrics.y;
+ self.dimensions.x = crate::size::max(self.dimensions.x, self.run.size.x);
+ self.dimensions.y += self.ctx.flex_spacing;
+ self.dimensions.y += self.run.size.y;
- // Reset the cursor the left and move down by the line and the inter-line
- // spacing.
self.cursor.x = self.ctx.space.padding.left;
- self.cursor.y += self.line_metrics.y + self.ctx.flex_spacing;
+ self.cursor.y += self.run.size.y + self.ctx.flex_spacing;
+ self.run.size = Size2D::zero();
+ }
- // Reset the current line metrics.
- self.line_metrics = Size2D::zero();
+ fn overflows(&self, line: Size) -> bool {
+ line > self.usable.x
+ }
+}
+
+impl FlexRun {
+ fn new() -> FlexRun {
+ FlexRun {
+ content: vec![],
+ size: Size2D::zero()
+ }
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index abf140d6..b760ca1e 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -267,7 +267,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
let boxed = layout.finish()?;
- self.stack_layouter.add_box(boxed)
+ self.stack_layouter.add(boxed)
}
/// Layout a function.
@@ -287,7 +287,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
for command in commands {
match command {
Command::Layout(tree) => self.layout(tree)?,
- Command::Add(layout) => self.stack_layouter.add_box(layout)?,
+ Command::Add(layout) => self.stack_layouter.add(layout)?,
Command::AddMany(layouts) => self.stack_layouter.add_many(layouts)?,
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
}
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs
index 5ca32970..3c9946a6 100644
--- a/src/layout/stacked.rs
+++ b/src/layout/stacked.rs
@@ -1,34 +1,41 @@
use super::*;
-/// Layouts boxes block-style.
-#[derive(Debug)]
+/// Stack-like layouting of boxes.
+///
+/// The boxes are arranged vertically, each layout gettings it's own "line".
pub struct StackLayouter {
ctx: StackContext,
actions: LayoutActionList,
- dimensions: Size2D,
usable: Size2D,
+ dimensions: Size2D,
cursor: Size2D,
}
+/// The context for the [`StackLayouter`].
#[derive(Debug, Copy, Clone)]
pub struct StackContext {
pub space: LayoutSpace,
}
impl StackLayouter {
- /// Create a new box layouter.
+ /// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter {
let space = ctx.space;
StackLayouter {
ctx,
actions: LayoutActionList::new(),
+
+ usable: ctx.space.usable(),
dimensions: match ctx.space.alignment {
Alignment::Left => Size2D::zero(),
Alignment::Right => Size2D::with_x(space.usable().x),
},
- usable: space.usable(),
+
cursor: Size2D::new(
+ // If left-align, the cursor points to the top-left corner of
+ // each box. If we right-align, it points to the top-right
+ // corner.
match ctx.space.alignment {
Alignment::Left => space.padding.left,
Alignment::Right => space.dimensions.x - space.padding.right,
@@ -43,22 +50,17 @@ impl StackLayouter {
&self.ctx
}
- /// Add a sublayout.
- pub fn add_box(&mut self, layout: Layout) -> LayoutResult<()> {
- // In the flow direction (vertical) add the layout and in the second
- // direction just consider the maximal size of any child layout.
- let new_size = Size2D {
+ /// Add a sublayout to the bottom.
+ pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
+ let new_dimensions = Size2D {
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
y: self.dimensions.y + layout.dimensions.y,
};
- // Check whether this box fits.
- if self.overflows(new_size) {
+ if self.overflows(new_dimensions) {
return Err(LayoutError::NotEnoughSpace);
}
- self.dimensions = new_size;
-
// Determine where to put the box. When we right-align it, we want the
// cursor to point to the top-right corner of the box. Therefore, the
// position has to be moved to the left by the width of the box.
@@ -68,28 +70,23 @@ impl StackLayouter {
};
self.cursor.y += layout.dimensions.y;
+ self.dimensions = new_dimensions;
- self.add_box_absolute(position, layout);
+ self.actions.add_layout(position, layout);
Ok(())
}
- /// Add multiple sublayouts.
+ /// Add multiple sublayouts from a multi-layout.
pub fn add_many(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
- self.add_box(layout)?;
+ self.add(layout)?;
}
Ok(())
}
- /// Add a sublayout at an absolute position.
- pub fn add_box_absolute(&mut self, position: Size2D, layout: Layout) {
- self.actions.add_box(position, layout);
- }
-
- /// Add space in between two boxes.
+ /// Add vertical space after the last layout.
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
- // Check whether this space fits.
if self.overflows(self.dimensions + Size2D::with_y(space)) {
return Err(LayoutError::NotEnoughSpace);
}
@@ -100,20 +97,7 @@ impl StackLayouter {
Ok(())
}
- /// The remaining space for new boxes.
- pub fn remaining(&self) -> Size2D {
- Size2D {
- x: self.usable.x,
- y: self.usable.y - self.dimensions.y,
- }
- }
-
- /// Whether this layouter contains any items.
- pub fn is_empty(&self) -> bool {
- self.actions.is_empty()
- }
-
- /// Finish the layouting and create a box layout from this.
+ /// Finish the layouting.
pub fn finish(self) -> Layout {
Layout {
dimensions: if self.ctx.space.shrink_to_fit {
@@ -126,8 +110,20 @@ impl StackLayouter {
}
}
- /// Whether the given box is bigger than what we can hold.
+ /// The remaining space for new layouts.
+ pub fn remaining(&self) -> Size2D {
+ Size2D {
+ x: self.usable.x,
+ y: self.usable.y - self.dimensions.y,
+ }
+ }
+
+ /// Whether this layouter contains any items.
+ pub fn is_empty(&self) -> bool {
+ self.actions.is_empty()
+ }
+
fn overflows(&self, dimensions: Size2D) -> bool {
- dimensions.x > self.usable.x || dimensions.y > self.usable.y
+ !self.usable.fits(dimensions)
}
}
diff --git a/src/size.rs b/src/size.rs
index e837a639..c7439439 100644
--- a/src/size.rs
+++ b/src/size.rs
@@ -130,6 +130,13 @@ impl Size2D {
y: self.y + padding.top + padding.bottom,
}
}
+
+ /// Whether the given [`Size2D`] fits into this one, that is,
+ /// both coordinate values are smaller.
+ #[inline]
+ pub fn fits(&self, other: Size2D) -> bool {
+ self.x >= other.x && self.y >= other.y
+ }
}
impl SizeBox {