diff options
| author | Martin Haug <mhaug@live.de> | 2022-04-30 21:59:34 +0200 |
|---|---|---|
| committer | Martin Haug <mhaug@live.de> | 2022-05-01 11:50:34 +0200 |
| commit | 5f1499d380e223e7e1b2a8a96eb99e3ec95a56ac (patch) | |
| tree | 44945bc35618c9ce10380016200309e42c2b5ed4 /src/library | |
| parent | f9e115daf54c29358f890b137f50a33a781af680 (diff) | |
Add round corners and change arguments
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/graphics/shape.rs | 174 |
1 files changed, 110 insertions, 64 deletions
diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 49c74c2f..7a1bfb1f 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -5,36 +5,46 @@ use crate::library::text::TextNode; /// Place a node into a sizable and fillable shape. #[derive(Debug, Hash)] -pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>); +pub struct AngularNode<const S: ShapeKind>(pub Option<LayoutNode>); /// Place a node into a square. -pub type SquareNode = ShapeNode<SQUARE>; +pub type SquareNode = AngularNode<SQUARE>; /// Place a node into a rectangle. -pub type RectNode = ShapeNode<RECT>; +pub type RectNode = AngularNode<RECT>; -/// Place a node into a circle. -pub type CircleNode = ShapeNode<CIRCLE>; +// /// Place a node into a sizable and fillable shape. +// #[derive(Debug, Hash)] +// pub struct RoundNode<const S: ShapeKind>(pub Option<LayoutNode>); -/// Place a node into an ellipse. -pub type EllipseNode = ShapeNode<ELLIPSE>; +// /// Place a node into a circle. +// pub type CircleNode = RoundNode<CIRCLE>; + +// /// Place a node into an ellipse. +// pub type EllipseNode = RoundNode<ELLIPSE>; #[node] -impl<const S: ShapeKind> ShapeNode<S> { +impl<const S: ShapeKind> AngularNode<S> { /// How to fill the shape. pub const FILL: Option<Paint> = None; /// How to stroke the shape. #[property(resolve, fold)] - pub const STROKE: Smart<Option<RawStroke>> = Smart::Auto; + pub const STROKE: Smart<Sides<Option<RawStroke>>> = Smart::Auto; + /// How much to pad the shape's content. - pub const PADDING: Relative<RawLength> = Relative::zero(); + #[property(resolve, fold)] + pub const INSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero()); + + /// How much to extend the shape's dimensions beyond the allocated space. + #[property(resolve, fold)] + pub const OUTSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero()); + + /// How much to round the shape's corners. + #[property(resolve, fold)] + pub const RADIUS: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero()); fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - let size = match S { - SQUARE => args.named::<RawLength>("size")?.map(Relative::from), - CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)), - _ => None, - }; + let size = args.named::<RawLength>("size")?.map(Relative::from); let width = match size { None => args.named("width")?, @@ -52,7 +62,50 @@ impl<const S: ShapeKind> ShapeNode<S> { } } -impl<const S: ShapeKind> Layout for ShapeNode<S> { +castable! { + Sides<Option<RawStroke>>, + Expected: "stroke, dictionary with strokes for each side", + Value::None => { + Sides::splat(None) + }, + Value::Dict(values) => { + let get = |name: &str| values.get(name.into()).and_then(|v| v.clone().cast()).unwrap_or(None); + Sides { + top: get("top"), + right: get("right"), + bottom: get("bottom"), + left: get("left"), + } + }, + Value::Length(thickness) => Sides::splat(Some(RawStroke { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + })), + Value::Color(color) => Sides::splat(Some(RawStroke { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + })), + @stroke: RawStroke => Sides::splat(Some(*stroke)), +} + +castable! { + Sides<Option<Relative<RawLength>>>, + Expected: "length or dictionary of lengths for each side", + Value::None => Sides::splat(None), + Value::Dict(values) => { + let get = |name: &str| values.get(name.into()).and_then(|v| v.clone().cast()).unwrap_or(None); + Sides { + top: get("top"), + right: get("right"), + bottom: get("bottom"), + left: get("left"), + } + }, + Value::Length(l) => Sides::splat(Some(l.into())), + Value::Relative(r) => Sides::splat(Some(r)), +} + +impl<const S: ShapeKind> Layout for AngularNode<S> { fn layout( &self, ctx: &mut Context, @@ -61,50 +114,43 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> { ) -> TypResult<Vec<Arc<Frame>>> { let mut frames; if let Some(child) = &self.0 { - let mut padding = styles.get(Self::PADDING); - if is_round(S) { - padding.rel += Ratio::new(0.5 - SQRT_2 / 4.0); - } + let inset = styles.get(Self::INSET); // Pad the child. - let child = child.clone().padded(Sides::splat(padding)); + let child = child + .clone() + .padded(inset.map(|side| side.map(|abs| RawLength::from(abs)))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); frames = child.layout(ctx, &pod, styles)?; // Relayout with full expansion into square region to make sure // the result is really a square or circle. - if is_quadratic(S) { - let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.first, Size::zero()); - target.x.max(target.y) - } else { - let size = frames[0].size; - let desired = size.x.max(size.y); - desired.min(regions.first.x).min(regions.first.y) - }; - - pod.first = Size::splat(length); - pod.expand = Spec::splat(true); - frames = child.layout(ctx, &pod, styles)?; - } + let length = if regions.expand.x || regions.expand.y { + let target = regions.expand.select(regions.first, Size::zero()); + target.x.max(target.y) + } else { + let size = frames[0].size; + let desired = size.x.max(size.y); + desired.min(regions.first.x).min(regions.first.y) + }; + + pod.first = Size::splat(length); + pod.expand = Spec::splat(true); + frames = child.layout(ctx, &pod, styles)?; } else { // The default size that a shape takes on if it has no child and // enough space. let mut size = Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first); - if is_quadratic(S) { - let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.first, Size::zero()); - target.x.max(target.y) - } else { - size.x.min(size.y) - }; - size = Size::splat(length); + let length = if regions.expand.x || regions.expand.y { + let target = regions.expand.select(regions.first, Size::zero()); + target.x.max(target.y) } else { - size = regions.expand.select(regions.first, size); - } + size.x.min(size.y) + }; + size = Size::splat(length); frames = vec![Arc::new(Frame::new(size))]; } @@ -114,18 +160,28 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> { // Add fill and/or stroke. let fill = styles.get(Self::FILL); let stroke = match styles.get(Self::STROKE) { - Smart::Auto => fill.is_none().then(Stroke::default), - Smart::Custom(stroke) => stroke.map(RawStroke::unwrap_or_default), + Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())), + Smart::Auto => Sides::splat(None), + Smart::Custom(strokes) => strokes.map(|s| Some(s.unwrap_or_default())), }; - if fill.is_some() || stroke.is_some() { - let geometry = if is_round(S) { - Geometry::Ellipse(frame.size) - } else { - Geometry::Rect(frame.size) - }; + let radius = { + let radius = styles.get(Self::RADIUS); - let shape = Shape { geometry, fill, stroke }; + Sides { + left: radius.left.relative_to(frame.size.x / 2.0), + top: radius.top.relative_to(frame.size.y / 2.0), + right: radius.right.relative_to(frame.size.x / 2.0), + bottom: radius.bottom.relative_to(frame.size.y / 2.0), + } + }; + + if fill.is_some() || stroke.iter().any(Option::is_some) { + let shape = Shape { + geometry: Geometry::Rect(frame.size, radius), + fill, + stroke, + }; frame.prepend(Point::zero(), Element::Shape(shape)); } @@ -152,13 +208,3 @@ const CIRCLE: ShapeKind = 2; /// A curve around two focal points. const ELLIPSE: ShapeKind = 3; - -/// Whether a shape kind is curvy. -fn is_round(kind: ShapeKind) -> bool { - matches!(kind, CIRCLE | ELLIPSE) -} - -/// Whether a shape kind has equal side length. -fn is_quadratic(kind: ShapeKind) -> bool { - matches!(kind, SQUARE | CIRCLE) -} |
