use crate::{Fill, Filter, Point, Region, Transformation};
#[cfg(feature = "web")]
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LineSegment {
Straight(Point),
InwardCurve(Point),
OutwardCurve(Point),
}
#[derive(Debug, Clone)]
pub enum Object {
Polygon(Point, Vec<LineSegment>),
Line(Point, Point, f32),
CurveOutward(Point, Point, f32),
CurveInward(Point, Point, f32),
SmallCircle(Point),
Dot(Point),
BigCircle(Point),
Text(Point, String, f32),
CenteredText(Point, String, f32),
Rectangle(Point, Point),
Image(Region, String),
RawSVG(Box<dyn svg::Node>),
}
impl Object {
pub fn color(self, fill: Fill) -> ColoredObject {
ColoredObject::from((self, Some(fill)))
}
pub fn filter(self, filter: Filter) -> ColoredObject {
ColoredObject::from((self, None)).filter(filter)
}
pub fn transform(self, transformation: Transformation) -> ColoredObject {
ColoredObject::from((self, None)).transform(transformation)
}
}
#[derive(Debug, Clone)]
pub struct ColoredObject {
pub object: Object,
pub fill: Option<Fill>,
pub filters: Vec<Filter>,
pub transformations: Vec<Transformation>,
}
impl ColoredObject {
pub fn filter(mut self, filter: Filter) -> Self {
self.filters.push(filter);
self
}
pub fn transform(mut self, transformation: Transformation) -> Self {
self.transformations.push(transformation);
self
}
pub fn clear_filters(&mut self) {
self.filters.clear();
}
}
impl std::fmt::Display for ColoredObject {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let ColoredObject {
object,
fill,
filters,
transformations,
} = self;
if fill.is_some() {
write!(f, "{:?} {:?}", fill.unwrap(), object)?;
} else {
write!(f, "transparent {:?}", object)?;
}
if !filters.is_empty() {
write!(f, " with filters {:?}", filters)?;
}
if !transformations.is_empty() {
write!(f, " with transformations {:?}", transformations)?;
}
Ok(())
}
}
impl From<Object> for ColoredObject {
fn from(value: Object) -> Self {
ColoredObject {
object: value,
fill: None,
filters: vec![],
transformations: vec![],
}
}
}
impl From<(Object, Option<Fill>)> for ColoredObject {
fn from((object, fill): (Object, Option<Fill>)) -> Self {
ColoredObject {
object,
fill,
filters: vec![],
transformations: vec![],
}
}
}
#[cfg_attr(feature = "web", wasm_bindgen)]
#[derive(Debug, Clone, Copy)]
pub struct ObjectSizes {
pub empty_shape_stroke_width: f32,
pub small_circle_radius: f32,
pub dot_radius: f32,
pub default_line_width: f32,
}
impl Default for ObjectSizes {
fn default() -> Self {
Self {
empty_shape_stroke_width: 0.5,
small_circle_radius: 5.0,
dot_radius: 2.0,
default_line_width: 2.0,
}
}
}
impl Object {
pub fn translate(&mut self, dx: i32, dy: i32) {
match self {
Object::Polygon(start, lines) => {
start.translate(dx, dy);
for line in lines {
match line {
LineSegment::InwardCurve(anchor)
| LineSegment::OutwardCurve(anchor)
| LineSegment::Straight(anchor) => anchor.translate(dx, dy),
}
}
}
Object::Line(start, end, _)
| Object::CurveInward(start, end, _)
| Object::CurveOutward(start, end, _)
| Object::Rectangle(start, end) => {
start.translate(dx, dy);
end.translate(dx, dy);
}
Object::Text(anchor, _, _)
| Object::CenteredText(anchor, ..)
| Object::Dot(anchor)
| Object::SmallCircle(anchor) => anchor.translate(dx, dy),
Object::BigCircle(center) => center.translate(dx, dy),
Object::Image(region, ..) => region.translate(dx, dy),
Object::RawSVG(_) => {
unimplemented!()
}
}
}
pub fn translate_with(&mut self, delta: (i32, i32)) {
self.translate(delta.0, delta.1)
}
pub fn teleport(&mut self, x: i32, y: i32) {
let Point(current_x, current_y) = self.region().start;
let delta_x = x - current_x as i32;
let delta_y = y - current_y as i32;
self.translate(delta_x, delta_y);
}
pub fn teleport_with(&mut self, position: (i32, i32)) {
self.teleport(position.0, position.1)
}
pub fn region(&self) -> Region {
match self {
Object::Polygon(start, lines) => {
let mut region: Region = (start, start).into();
for line in lines {
match line {
LineSegment::InwardCurve(anchor)
| LineSegment::OutwardCurve(anchor)
| LineSegment::Straight(anchor) => {
region = *region.max(&(start, anchor).into())
}
}
}
region
}
Object::Line(start, end, _)
| Object::CurveInward(start, end, _)
| Object::CurveOutward(start, end, _)
| Object::Rectangle(start, end) => (start, end).into(),
Object::Text(anchor, _, _)
| Object::CenteredText(anchor, ..)
| Object::Dot(anchor)
| Object::SmallCircle(anchor) => anchor.region(),
Object::BigCircle(center) => center.region(),
Object::Image(region, ..) => *region,
Object::RawSVG(_) => {
unimplemented!()
}
}
}
pub fn fillable(&self) -> bool {
!matches!(
self,
Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..)
)
}
pub fn hatchable(&self) -> bool {
self.fillable() && !matches!(self, Object::Dot(..))
}
}