summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2022-04-30 21:59:34 +0200
committerMartin Haug <mhaug@live.de>2022-05-01 11:50:34 +0200
commit5f1499d380e223e7e1b2a8a96eb99e3ec95a56ac (patch)
tree44945bc35618c9ce10380016200309e42c2b5ed4 /src/library
parentf9e115daf54c29358f890b137f50a33a781af680 (diff)
Add round corners and change arguments
Diffstat (limited to 'src/library')
-rw-r--r--src/library/graphics/shape.rs174
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)
-}