use crate::foundations::{Cast, Content, Smart, elem};
use crate::layout::{Abs, Corners, Length, Point, Rect, Rel, Sides, Size, Sizing};
use crate::visualize::{Curve, FixedStroke, Paint, Stroke};
#[elem(title = "Rectangle")]
pub struct RectElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
pub fill: Option<Paint>,
#[fold]
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
pub body: Option<Content>,
}
#[elem]
pub struct SquareElem {
#[external]
pub size: Smart<Length>,
#[parse(
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
match size {
None => args.named("width")?,
size => size,
}
)]
pub width: Smart<Rel<Length>>,
#[parse(match size {
None => args.named("height")?,
size => size.map(Into::into),
})]
pub height: Sizing,
pub fill: Option<Paint>,
#[fold]
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
pub body: Option<Content>,
}
#[elem]
pub struct EllipseElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
pub fill: Option<Paint>,
#[fold]
pub stroke: Smart<Option<Stroke>>,
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
pub body: Option<Content>,
}
#[elem]
pub struct CircleElem {
#[external]
pub radius: Length,
#[parse(
let size = args
.named::<Smart<Length>>("radius")?
.map(|s| s.map(|r| 2.0 * Rel::from(r)));
match size {
None => args.named("width")?,
size => size,
}
)]
pub width: Smart<Rel<Length>>,
#[parse(match size {
None => args.named("height")?,
size => size.map(Into::into),
})]
pub height: Sizing,
pub fill: Option<Paint>,
#[fold]
#[default(Smart::Auto)]
pub stroke: Smart<Option<Stroke>>,
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
pub body: Option<Content>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Shape {
pub geometry: Geometry,
pub fill: Option<Paint>,
pub fill_rule: FillRule,
pub stroke: Option<FixedStroke>,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum FillRule {
#[default]
NonZero,
EvenOdd,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Geometry {
Line(Point),
Rect(Size),
Curve(Curve),
}
impl Geometry {
pub fn filled(self, fill: impl Into<Paint>) -> Shape {
Shape {
geometry: self,
fill: Some(fill.into()),
fill_rule: FillRule::default(),
stroke: None,
}
}
pub fn stroked(self, stroke: FixedStroke) -> Shape {
Shape {
geometry: self,
fill: None,
fill_rule: FillRule::default(),
stroke: Some(stroke),
}
}
pub fn bbox(&self) -> Rect {
match self {
Self::Line(end) => {
let min = end.min(Point::zero());
let max = end.max(Point::zero());
Rect::new(min, max)
}
Self::Rect(size) => {
let p = size.to_point();
let min = p.min(Point::zero());
let max = p.max(Point::zero());
Rect::new(min, max)
}
Self::Curve(curve) => curve.bbox(),
}
}
pub fn bbox_size(&self) -> Size {
match self {
Self::Line(line) => Size::new(line.x, line.y),
Self::Rect(rect) => *rect,
Self::Curve(curve) => curve.bbox_size(),
}
}
}