use crate::drawing::*;
use crate::{Path2D, Rect, ValidationError};
mod primitives;
pub use primitives::*;
pub trait Shape: Send + Sync {
fn validate(&self) -> Result<(), ValidationError> {
Ok(())
}
fn path(&self) -> Path2D;
fn bounding_box(&self) -> Rect {
self.path().bounding_box()
}
fn as_rect(&self) -> Option<Rect> {
None
}
fn fill(self, paint: impl Into<Paint>) -> Styled<Self>
where
Self: Sized,
{
Styled {
shape: self,
fill: Some(paint.into()),
stroke: None,
stroke_style: StrokeStyle::default(),
rule: FillRule::default(),
trim: None,
dash: None,
}
}
fn stroke(self, width: f64, paint: impl Into<Paint>) -> Styled<Self>
where
Self: Sized,
{
Styled {
shape: self,
fill: None,
stroke: Some(paint.into()),
stroke_style: StrokeStyle {
width,
..StrokeStyle::default()
},
rule: FillRule::default(),
trim: None,
dash: None,
}
}
}
impl Shape for Path2D {
fn validate(&self) -> Result<(), ValidationError> {
Path2D::validate(self)
}
fn path(&self) -> Path2D {
self.clone()
}
fn bounding_box(&self) -> Rect {
Path2D::bounding_box(self)
}
}
impl Shape for Rect {
fn path(&self) -> Path2D {
Path2D::from_rect(*self).expect("Shape::path requires valid rectangle geometry")
}
fn bounding_box(&self) -> Rect {
*self
}
fn as_rect(&self) -> Option<Rect> {
Some(*self)
}
}
#[derive(Clone, Debug)]
pub struct Styled<Geometry> {
shape: Geometry,
fill: Option<Paint>,
stroke: Option<Paint>,
stroke_style: StrokeStyle,
rule: FillRule,
trim: Option<(f64, f64)>,
dash: Option<(std::sync::Arc<[f64]>, f64)>,
}
impl<Geometry: Shape> Styled<Geometry> {
pub fn trim(mut self, start: f64, end: f64) -> Self {
self.trim = Some((start, end));
self
}
pub fn dash(mut self, pattern: &[f64], offset: f64) -> Self {
self.dash = Some((std::sync::Arc::from(pattern), offset));
self
}
pub fn fill(mut self, paint: impl Into<Paint>) -> Self {
self.fill = Some(paint.into());
self
}
pub fn stroke(mut self, width: f64, paint: impl Into<Paint>) -> Self {
self.stroke = Some(paint.into());
self.stroke_style.width = width;
self
}
pub fn stroke_style(mut self, style: StrokeStyle) -> Self {
self.stroke_style = style;
self
}
pub fn fill_rule(mut self, rule: FillRule) -> Self {
self.rule = rule;
self
}
}
impl Rect {
pub fn fill(self, paint: impl Into<Paint>) -> Styled<Self> {
Shape::fill(self, paint)
}
pub fn stroke(self, width: f64, paint: impl Into<Paint>) -> Styled<Self> {
Shape::stroke(self, width, paint)
}
}
impl<Geometry: Shape> Drawable for Styled<Geometry> {
fn try_bounds(&self, context: &DrawContext) -> Result<Rect, ValidationError> {
self.shape.validate()?;
Ok(self.bounds(context))
}
fn bounds(&self, _: &DrawContext) -> Rect {
let bounds = self.shape.bounding_box();
if self.stroke.is_some() && validate_rect(bounds, "stroke", "bounds").is_ok() {
if self.stroke_style.validate("stroke").is_err() {
return Rect::new(f64::NAN, 0.0, 0.0, 0.0);
}
self.stroke_style.bounds(bounds)
} else {
bounds
}
}
fn draw(&self, _: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
self.shape.validate()?;
if self.trim.is_some() || self.dash.is_some() {
let mut geometry = self.shape.path();
if let Some((start, end)) = self.trim {
geometry = geometry.try_trim(start, end)?;
}
if let Some((pattern, offset)) = &self.dash {
geometry = geometry.dashed(pattern, *offset)?;
}
if let Some(paint) = &self.fill {
canvas.fill(&geometry, paint.clone(), self.rule)?;
}
if let Some(paint) = &self.stroke {
canvas.stroke(&geometry, &self.stroke_style, paint.clone())?;
}
return Ok(());
}
if let Some(paint) = &self.fill {
canvas.fill(&self.shape, paint.clone(), self.rule)?;
}
if let Some(paint) = &self.stroke {
canvas.stroke(&self.shape, &self.stroke_style, paint.clone())?;
}
Ok(())
}
}