From 5f1499d380e223e7e1b2a8a96eb99e3ec95a56ac Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sat, 30 Apr 2022 21:59:34 +0200 Subject: Add round corners and change arguments --- src/library/graphics/shape.rs | 174 ++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 64 deletions(-) (limited to 'src/library') 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(pub Option); +pub struct AngularNode(pub Option); /// Place a node into a square. -pub type SquareNode = ShapeNode; +pub type SquareNode = AngularNode; /// Place a node into a rectangle. -pub type RectNode = ShapeNode; +pub type RectNode = AngularNode; -/// Place a node into a circle. -pub type CircleNode = ShapeNode; +// /// Place a node into a sizable and fillable shape. +// #[derive(Debug, Hash)] +// pub struct RoundNode(pub Option); -/// Place a node into an ellipse. -pub type EllipseNode = ShapeNode; +// /// Place a node into a circle. +// pub type CircleNode = RoundNode; + +// /// Place a node into an ellipse. +// pub type EllipseNode = RoundNode; #[node] -impl ShapeNode { +impl AngularNode { /// How to fill the shape. pub const FILL: Option = None; /// How to stroke the shape. #[property(resolve, fold)] - pub const STROKE: Smart> = Smart::Auto; + pub const STROKE: Smart>> = Smart::Auto; + /// How much to pad the shape's content. - pub const PADDING: Relative = Relative::zero(); + #[property(resolve, fold)] + pub const INSET: Sides>> = Sides::splat(Relative::zero()); + + /// How much to extend the shape's dimensions beyond the allocated space. + #[property(resolve, fold)] + pub const OUTSET: Sides>> = Sides::splat(Relative::zero()); + + /// How much to round the shape's corners. + #[property(resolve, fold)] + pub const RADIUS: Sides>> = Sides::splat(Relative::zero()); fn construct(_: &mut Context, args: &mut Args) -> TypResult { - let size = match S { - SQUARE => args.named::("size")?.map(Relative::from), - CIRCLE => args.named::("radius")?.map(|r| 2.0 * Relative::from(r)), - _ => None, - }; + let size = args.named::("size")?.map(Relative::from); let width = match size { None => args.named("width")?, @@ -52,7 +62,50 @@ impl ShapeNode { } } -impl Layout for ShapeNode { +castable! { + Sides>, + 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>>, + 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 Layout for AngularNode { fn layout( &self, ctx: &mut Context, @@ -61,50 +114,43 @@ impl Layout for ShapeNode { ) -> TypResult>> { 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 Layout for ShapeNode { // 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) -} -- cgit v1.2.3