diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-10-16 21:31:14 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-10-16 21:31:14 +0200 |
| commit | f2f05e07b0ff2d98e3c822b2618d02281ed1078c (patch) | |
| tree | 6f4f8fa046af49c319d68c012a078f3489ab92aa /src/layout/stacked.rs | |
| parent | a3c667895e4e5d5673931415397523b9615008d3 (diff) | |
Implement space extension (multipage) ➕
Diffstat (limited to 'src/layout/stacked.rs')
| -rw-r--r-- | src/layout/stacked.rs | 135 |
1 files changed, 104 insertions, 31 deletions
diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index bfca4e3e..367a03d7 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -5,44 +5,38 @@ use super::*; /// The boxes are arranged vertically, each layout gettings it's own "line". pub struct StackLayouter { ctx: StackContext, + layouts: MultiLayout, actions: LayoutActionList, + + space: LayoutSpace, usable: Size2D, dimensions: Size2D, cursor: Size2D, + in_extra_space: bool, + started: bool, } /// The context for stack layouting. #[derive(Debug, Copy, Clone)] pub struct StackContext { - /// The space to layout the boxes in. pub space: LayoutSpace, + pub extra_space: Option<LayoutSpace>, } impl StackLayouter { /// Create a new stack layouter. pub fn new(ctx: StackContext) -> StackLayouter { - let space = ctx.space; - StackLayouter { ctx, + layouts: MultiLayout::new(), actions: LayoutActionList::new(), + space: ctx.space, usable: ctx.space.usable(), - dimensions: match ctx.space.alignment { - Alignment::Left => Size2D::zero(), - Alignment::Right => Size2D::with_x(space.usable().x), - }, - - 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, - }, - space.padding.top, - ), + dimensions: start_dimensions(ctx.space), + cursor: start_cursor(ctx.space), + in_extra_space: false, + started: true, } } @@ -53,19 +47,30 @@ impl StackLayouter { /// Add a sublayout to the bottom. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + if !self.started { + self.start_new_space()?; + } + let new_dimensions = Size2D { x: crate::size::max(self.dimensions.x, layout.dimensions.x), y: self.dimensions.y + layout.dimensions.y, }; if self.overflows(new_dimensions) { - return Err(LayoutError::NotEnoughSpace); + if self.ctx.extra_space.is_some() && + !(self.in_extra_space && self.overflows(layout.dimensions)) + { + self.finish_layout(true)?; + return self.add(layout); + } else { + return Err(LayoutError::NotEnoughSpace("cannot fit box into stack")); + } } // 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. - let position = match self.ctx.space.alignment { + let position = match self.space.alignment { Alignment::Left => self.cursor, Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x), }; @@ -88,26 +93,74 @@ impl StackLayouter { /// Add vertical space after the last layout. pub fn add_space(&mut self, space: Size) -> LayoutResult<()> { - if self.overflows(self.dimensions + Size2D::with_y(space)) { - return Err(LayoutError::NotEnoughSpace); + if !self.started { + self.start_new_space()?; } - self.cursor.y += space; - self.dimensions.y += space; + let new_dimensions = self.dimensions + Size2D::with_y(space); + + if self.overflows(new_dimensions) { + if self.ctx.extra_space.is_some() { + self.finish_layout(false)?; + } else { + return Err(LayoutError::NotEnoughSpace("cannot fit space into stack")); + } + } else { + self.cursor.y += space; + self.dimensions.y += space; + } Ok(()) } /// Finish the layouting. - pub fn finish(self) -> Layout { - Layout { - dimensions: if self.ctx.space.shrink_to_fit { - self.dimensions.padded(self.ctx.space.padding) + /// + /// The layouter is not consumed by this to prevent ownership problems. + /// It should not be used further. + pub fn finish(&mut self) -> LayoutResult<MultiLayout> { + if self.started { + self.finish_layout(false)?; + } + Ok(std::mem::replace(&mut self.layouts, MultiLayout::new())) + } + + /// Finish the current layout and start a new one in an extra space + /// (if there is an extra space). + /// + /// If `start_new_empty` is true, a new empty layout will be started. Otherwise, + /// the new layout only emerges when new content is added. + pub fn finish_layout(&mut self, start_new_empty: bool) -> LayoutResult<()> { + let actions = std::mem::replace(&mut self.actions, LayoutActionList::new()); + self.layouts.add(Layout { + dimensions: if self.space.shrink_to_fit { + self.dimensions.padded(self.space.padding) } else { - self.ctx.space.dimensions + self.space.dimensions }, - actions: self.actions.into_vec(), + actions: actions.into_vec(), debug_render: true, + }); + + self.started = false; + + if start_new_empty { + self.start_new_space()?; + } + + Ok(()) + } + + pub fn start_new_space(&mut self) -> LayoutResult<()> { + if let Some(space) = self.ctx.extra_space { + self.started = true; + self.space = space; + self.usable = space.usable(); + self.dimensions = start_dimensions(space); + self.cursor = start_cursor(space); + self.in_extra_space = true; + Ok(()) + } else { + Err(LayoutError::NotEnoughSpace("no extra space to start")) } } @@ -121,10 +174,30 @@ impl StackLayouter { /// Whether this layouter contains any items. pub fn is_empty(&self) -> bool { - self.actions.is_empty() + self.layouts.is_empty() && self.actions.is_empty() } fn overflows(&self, dimensions: Size2D) -> bool { !self.usable.fits(dimensions) } } + +fn start_dimensions(space: LayoutSpace) -> Size2D { + match space.alignment { + Alignment::Left => Size2D::zero(), + Alignment::Right => Size2D::with_x(space.usable().x), + } +} + +fn start_cursor(space: LayoutSpace) -> Size2D { + Size2D { + // 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. + x: match space.alignment { + Alignment::Left => space.padding.left, + Alignment::Right => space.dimensions.x - space.padding.right, + }, + y: space.padding.top, + } +} |
