diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
| commit | 898728f260923a91444eb23b522d0abf01a4299b (patch) | |
| tree | 64fdd3f78e16e6428e765a8e2d99c3cd910bd9df /src/layout | |
| parent | 6cb9fe9064a037224b6560b69b441b72e787fa94 (diff) | |
Square, circle and ellipse 🔵
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/background.rs | 29 | ||||
| -rw-r--r-- | src/layout/fixed.rs | 16 | ||||
| -rw-r--r-- | src/layout/frame.rs | 8 | ||||
| -rw-r--r-- | src/layout/mod.rs | 47 | ||||
| -rw-r--r-- | src/layout/pad.rs | 35 | ||||
| -rw-r--r-- | src/layout/par.rs | 16 | ||||
| -rw-r--r-- | src/layout/stack.rs | 35 |
7 files changed, 133 insertions, 53 deletions
diff --git a/src/layout/background.rs b/src/layout/background.rs index bb155073..17280a86 100644 --- a/src/layout/background.rs +++ b/src/layout/background.rs @@ -3,25 +3,38 @@ use super::*; /// A node that places a rectangular filled background behind its child. #[derive(Debug, Clone, PartialEq)] pub struct BackgroundNode { + /// The kind of shape to use as a background. + pub shape: BackgroundShape, /// The background fill. pub fill: Fill, /// The child node to be filled. pub child: Node, } +/// The kind of shape to use as a background. +#[derive(Debug, Clone, PartialEq)] +pub enum BackgroundShape { + Rect, + Ellipse, +} + impl Layout for BackgroundNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment { - let mut layouted = self.child.layout(ctx, areas); + let mut fragment = self.child.layout(ctx, areas); + + for frame in fragment.frames_mut() { + let (point, shape) = match self.shape { + BackgroundShape::Rect => (Point::ZERO, Shape::Rect(frame.size)), + BackgroundShape::Ellipse => { + (frame.size.to_point() / 2.0, Shape::Ellipse(frame.size)) + } + }; - for frame in layouted.frames_mut() { - let element = Element::Geometry(Geometry { - shape: Shape::Rect(frame.size), - fill: self.fill, - }); - frame.elements.insert(0, (Point::ZERO, element)); + let element = Element::Geometry(Geometry { shape, fill: self.fill }); + frame.elements.insert(0, (point, element)); } - layouted + fragment } } diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index e3365668..22c45ef1 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -7,6 +7,10 @@ pub struct FixedNode { pub width: Option<Linear>, /// The fixed height, if any. pub height: Option<Linear>, + /// The fixed aspect ratio between width and height, if any. + /// + /// The resulting frame will satisfy `width = aspect * height`. + pub aspect: Option<f64>, /// The child node whose size to fix. pub child: Node, } @@ -14,18 +18,26 @@ pub struct FixedNode { impl Layout for FixedNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment { let Areas { current, full, .. } = areas; - let size = Size::new( + + let full = Size::new( self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width), self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height), ); + let mut size = full; + if let Some(aspect) = self.aspect { + // Shrink the size to ensure that the aspect ratio can be satisfied. + let width = size.width.min(aspect * size.height); + size = Size::new(width, width / aspect); + } + let fill_if = |cond| if cond { Expand::Fill } else { Expand::Fit }; let expand = Spec::new( fill_if(self.width.is_some()), fill_if(self.height.is_some()), ); - let areas = Areas::once(size, expand); + let areas = Areas::once(size, full, expand).with_aspect(self.aspect); self.child.layout(ctx, &areas) } } diff --git a/src/layout/frame.rs b/src/layout/frame.rs index c85d7539..6e876151 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -1,7 +1,7 @@ use super::Shaped; use crate::color::Color; use crate::env::ResourceId; -use crate::geom::{Point, Size}; +use crate::geom::{Path, Point, Size}; /// A finished layout with elements at fixed positions. #[derive(Debug, Clone, PartialEq)] @@ -59,8 +59,12 @@ pub struct Geometry { /// Some shape. #[derive(Debug, Clone, PartialEq)] pub enum Shape { - /// A rectangle. + /// A rectangle with its origin in the topleft corner. Rect(Size), + /// An ellipse with its origin in the center. + Ellipse(Size), + /// A bezier path. + Path(Path), } /// The kind of graphic fill to be applied to a [`Shape`]. diff --git a/src/layout/mod.rs b/src/layout/mod.rs index ae4ab89d..360c9d84 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -58,8 +58,7 @@ impl PageRun { /// Layout the page run. pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> { let areas = Areas::repeat(self.size, Spec::uniform(Expand::Fill)); - let layouted = self.child.layout(ctx, &areas); - layouted.into_frames() + self.child.layout(ctx, &areas).into_frames() } } @@ -89,29 +88,59 @@ pub struct Areas { pub last: Option<Size>, /// Whether the frames resulting from layouting into this areas should be /// shrunk to fit their content or expanded to fill the area. + /// + /// This property is handled partially by the par layouter and fully by the + /// stack layouter. pub expand: Spec<Expand>, + /// The aspect ratio the resulting frame should respect. + /// + /// This property is only handled by the stack layouter. + pub aspect: Option<f64>, } impl Areas { - /// Create a new length-1 sequence of areas with just one `area`. - pub fn once(size: Size, expand: Spec<Expand>) -> Self { + /// Create a new sequence of areas that repeats `area` indefinitely. + pub fn repeat(size: Size, expand: Spec<Expand>) -> Self { Self { current: size, full: size, backlog: vec![], - last: None, + last: Some(size), expand, + aspect: None, } } - /// Create a new sequence of areas that repeats `area` indefinitely. - pub fn repeat(size: Size, expand: Spec<Expand>) -> Self { + /// Create a new length-1 sequence of areas with just one `area`. + pub fn once(size: Size, full: Size, expand: Spec<Expand>) -> Self { Self { current: size, - full: size, + full, backlog: vec![], - last: Some(size), + last: None, expand, + aspect: None, + } + } + + /// Builder-style method for setting the aspect ratio. + pub fn with_aspect(mut self, aspect: Option<f64>) -> Self { + self.aspect = aspect; + self + } + + /// Map all areas. + pub fn map<F>(&self, mut f: F) -> Self + where + F: FnMut(Size) -> Size, + { + Self { + current: f(self.current), + full: f(self.full), + backlog: self.backlog.iter().copied().map(|s| f(s)).collect(), + last: self.last.map(f), + expand: self.expand, + aspect: self.aspect, } } diff --git a/src/layout/pad.rs b/src/layout/pad.rs index 33ce217d..fb038996 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -13,12 +13,12 @@ impl Layout for PadNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment { let areas = shrink(areas, self.padding); - let mut layouted = self.child.layout(ctx, &areas); - for frame in layouted.frames_mut() { + let mut fragment = self.child.layout(ctx, &areas); + for frame in fragment.frames_mut() { pad(frame, self.padding); } - layouted + fragment } } @@ -30,23 +30,30 @@ impl From<PadNode> for AnyNode { /// Shrink all areas by the padding. fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas { - let shrink = |size| size - padding.resolve(size).size(); - Areas { - current: shrink(areas.current), - full: shrink(areas.full), - backlog: areas.backlog.iter().copied().map(shrink).collect(), - last: areas.last.map(shrink), - expand: areas.expand, - } + areas.map(|size| size - padding.resolve(size).size()) } -/// Enlarge the frame and move all elements inwards. +/// Pad the frame and move all elements inwards. fn pad(frame: &mut Frame, padding: Sides<Linear>) { - let padding = padding.resolve(frame.size); + let padded = solve(padding, frame.size); + let padding = padding.resolve(padded); let origin = Point::new(padding.left, padding.top); - frame.size += padding.size(); + frame.size = padded; for (point, _) in &mut frame.elements { *point += origin; } } + +/// Solve for the size `padded` that satisfies (approximately): +/// `padded - padding.resolve(padded).size() == size` +fn solve(padding: Sides<Linear>, size: Size) -> Size { + fn solve_axis(length: Length, padding: Linear) -> Length { + (length + padding.abs) / (1.0 - padding.rel.get()) + } + + Size::new( + solve_axis(size.width, padding.left + padding.right), + solve_axis(size.height, padding.top + padding.bottom), + ) +} diff --git a/src/layout/par.rs b/src/layout/par.rs index e9fda015..0364a03a 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -18,7 +18,7 @@ pub struct ParNode { impl Layout for ParNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment { - let mut layouter = ParLayouter::new(self, areas.clone()); + let mut layouter = ParLayouter::new(self.dirs, self.line_spacing, areas.clone()); for child in &self.children { match child.layout(ctx, &layouter.areas) { Fragment::Spacing(spacing) => layouter.push_spacing(spacing), @@ -57,12 +57,12 @@ struct ParLayouter { } impl ParLayouter { - fn new(par: &ParNode, areas: Areas) -> Self { + fn new(dirs: LayoutDirs, line_spacing: Length, areas: Areas) -> Self { Self { - main: par.dirs.main.axis(), - cross: par.dirs.cross.axis(), - dirs: par.dirs, - line_spacing: par.line_spacing, + main: dirs.main.axis(), + cross: dirs.cross.axis(), + dirs, + line_spacing, areas, finished: vec![], lines: vec![], @@ -134,12 +134,12 @@ impl ParLayouter { } fn finish_line(&mut self) { - let expand = self.areas.expand.switch(self.dirs); let full_size = { + let expand = self.areas.expand.switch(self.dirs); let full = self.areas.full.switch(self.dirs); Gen::new( self.line_size.main, - expand.cross.resolve(self.line_size.cross.min(full.cross), full.cross), + expand.cross.resolve(self.line_size.cross, full.cross), ) }; diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 32eba676..6a87290e 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -16,7 +16,7 @@ pub struct StackNode { impl Layout for StackNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment { - let mut layouter = StackLayouter::new(self, areas.clone()); + let mut layouter = StackLayouter::new(self.dirs, areas.clone()); for child in &self.children { match child.layout(ctx, &layouter.areas) { Fragment::Spacing(spacing) => layouter.push_spacing(spacing), @@ -49,10 +49,10 @@ struct StackLayouter { } impl StackLayouter { - fn new(stack: &StackNode, areas: Areas) -> Self { + fn new(dirs: LayoutDirs, areas: Areas) -> Self { Self { - main: stack.dirs.main.axis(), - dirs: stack.dirs, + main: dirs.main.axis(), + dirs, areas, finished: vec![], frames: vec![], @@ -93,12 +93,27 @@ impl StackLayouter { fn finish_area(&mut self) { let full_size = { - let expand = self.areas.expand.switch(self.dirs); - let full = self.areas.full.switch(self.dirs); - Gen::new( - expand.main.resolve(self.used.main.min(full.main), full.main), - expand.cross.resolve(self.used.cross.min(full.cross), full.cross), - ) + let expand = self.areas.expand; + let full = self.areas.full; + let current = self.areas.current; + let used = self.used.switch(self.dirs).to_size(); + + let mut size = Size::new( + expand.horizontal.resolve(used.width, full.width), + expand.vertical.resolve(used.height, full.height), + ); + + if let Some(aspect) = self.areas.aspect { + let width = size + .width + .max(aspect * size.height) + .min(current.width) + .min((current.height + used.height) / aspect); + + size = Size::new(width, width / aspect); + } + + size.switch(self.dirs) }; let mut output = Frame::new(full_size.switch(self.dirs).to_size()); |
