diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-10-13 18:33:10 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-10-23 20:22:47 +0200 |
| commit | 1e74f7c407e42174b631cb7477f3c88252da7e25 (patch) | |
| tree | 53c0510b6503c434c9ba470b188d9e737ce1c3cf /src/layout/shape.rs | |
| parent | 5f4dde0a6b32c7620b29af30f69591cf3995af9b (diff) | |
New `ShapeNode`
Replaces `BackgroundNode` and `FixedNode`
Diffstat (limited to 'src/layout/shape.rs')
| -rw-r--r-- | src/layout/shape.rs | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/layout/shape.rs b/src/layout/shape.rs new file mode 100644 index 00000000..aa90707c --- /dev/null +++ b/src/layout/shape.rs @@ -0,0 +1,141 @@ +use std::f64::consts::SQRT_2; + +use super::*; + +/// Places its child into a sizable and fillable shape. +#[derive(Debug)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] +pub struct ShapeNode { + /// Which shape to place the child into. + pub shape: ShapeKind, + /// The width, if any. + pub width: Option<Linear>, + /// The height, if any. + pub height: Option<Linear>, + /// How to fill the shape, if at all. + pub fill: Option<Paint>, + /// The child node to place into the shape, if any. + pub child: Option<LayoutNode>, +} + +/// The type of a shape. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum ShapeKind { + /// A rectangle with equal side lengths. + Square, + /// A quadrilateral with four right angles. + Rect, + /// An ellipse with coinciding foci. + Circle, + /// A curve around two focal points. + Ellipse, +} + +impl Layout for ShapeNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>> { + // Resolve width and height relative to the region's base. + let width = self.width.map(|w| w.resolve(regions.base.w)); + let height = self.height.map(|h| h.resolve(regions.base.h)); + + // Generate constraints. + let constraints = { + let mut cts = Constraints::new(regions.expand); + cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); + + // Set exact and base constraint if child is automatically sized. + if self.width.is_none() { + cts.exact.x = Some(regions.current.w); + cts.base.x = Some(regions.base.w); + } + + // Same here. + if self.height.is_none() { + cts.exact.y = Some(regions.current.h); + cts.base.y = Some(regions.base.h); + } + + cts + }; + + // Layout. + let mut frames = if let Some(child) = &self.child { + let mut node: &dyn Layout = child; + + let padded; + if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) { + // Padding with this ratio ensures that a rectangular child fits + // perfectly into a circle / an ellipse. + padded = PadNode { + padding: Sides::splat(Relative::new(0.5 - SQRT_2 / 4.0).into()), + child: child.clone(), + }; + node = &padded; + } + + // The "pod" is the region into which the child will be layouted. + let mut pod = { + let size = Size::new( + width.unwrap_or(regions.current.w), + height.unwrap_or(regions.current.h), + ); + + let base = Size::new( + if width.is_some() { size.w } else { regions.base.w }, + if height.is_some() { size.h } else { regions.base.h }, + ); + + let expand = Spec::new(width.is_some(), height.is_some()); + Regions::one(size, base, expand) + }; + + // Now, layout the child. + let mut frames = node.layout(ctx, &pod); + + if matches!(self.shape, ShapeKind::Square | ShapeKind::Circle) { + // Relayout with full expansion into square region to make sure + // the result is really a square or circle. + let size = frames[0].item.size; + pod.current.w = size.w.max(size.h).min(pod.current.w); + pod.current.h = pod.current.w; + pod.expand = Spec::splat(true); + frames = node.layout(ctx, &pod); + } + + // Validate and set constraints. + assert_eq!(frames.len(), 1); + frames[0].constraints = constraints; + frames + } else { + // Resolve shape size. + let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default()); + vec![Frame::new(size, size.h).constrain(constraints)] + }; + + // Add background shape if desired. + if let Some(fill) = self.fill { + let frame = Rc::make_mut(&mut frames[0].item); + let (pos, geometry) = match self.shape { + ShapeKind::Square | ShapeKind::Rect => { + (Point::zero(), Geometry::Rect(frame.size)) + } + ShapeKind::Circle | ShapeKind::Ellipse => { + (frame.size.to_point() / 2.0, Geometry::Ellipse(frame.size)) + } + }; + + frame.prepend(pos, Element::Geometry(geometry, fill)); + } + + frames + } +} + +impl From<ShapeNode> for LayoutNode { + fn from(shape: ShapeNode) -> Self { + Self::new(shape) + } +} |
