// Stiletto // Copyright (C) 2020 Stiletto Authors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use std::vec::Vec; use im::{vector, Vector}; use serde::de::{Deserializer, SeqAccess, Visitor}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, druid::Data)] pub struct Path { pub kurbo_path: druid::kurbo::BezPath, } struct Interval { start_pos: f64, end_pos: f64, } impl Interval { fn new(x1: f64, x2: f64) -> Interval { Interval { start_pos: x1.min(x2), end_pos: x1.max(x2), } } fn new_ordered(start: f64, end: f64) -> Interval { Interval { start_pos: start, end_pos: end, } } fn overlaps(&self, other: &Self) -> bool { self.start_pos < other.end_pos && other.start_pos < self.end_pos } } // O(1) complexity // TODO(francesco): Implement this in a way that is not so flamboyant // TODO(francesco): Implement intersection test for bezier curves fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -> bool { match segment { druid::kurbo::PathSeg::Line(line) => { // A Segment intersects the rect // if their projections on the x and y line both overlap let line_x_proj = Interval::new(line.p0.x, line.p1.x); let line_y_proj = Interval::new(line.p0.y, line.p1.y); let rect_x_proj = Interval::new_ordered(rect.min_x(), rect.max_x()); let rect_y_proj = Interval::new_ordered(rect.min_y(), rect.max_y()); line_x_proj.overlaps(&rect_x_proj) && line_y_proj.overlaps(&rect_y_proj) } _ => { unimplemented!(); } } } #[derive(Debug, Clone, druid::Data, Serialize, Deserialize)] pub enum CanvasElement { Freehand { path: Path, thickness: f64, #[serde( serialize_with = "serialize_color", deserialize_with = "deserialize_color" )] stroke_color: druid::Color, }, } fn serialize_color(c: &druid::Color, ser: S) -> Result where S: Serializer, { ser.serialize_u32(c.as_rgba_u32()) } fn deserialize_color<'de, D>(de: D) -> Result where D: Deserializer<'de>, { let c = u32::deserialize(de)?; Ok(druid::Color::from_rgba32_u32(c)) } impl CanvasElement { pub fn bounding_box(&self) -> druid::Rect { match self { CanvasElement::Freehand { path, thickness, .. } => { use druid::kurbo::Shape; path.kurbo_path .bounding_box() .inflate(*thickness, *thickness) } } } pub fn intersects_rect(&self, rect: druid::Rect) -> bool { match self { CanvasElement::Freehand { path, .. } => { // TODO(enrico) this doesn't handle thickness path.kurbo_path .segments() .any(|segment| segment_intersects_rect(&segment, rect)) } } } pub fn draw(&self, ctx: &mut druid::PaintCtx) { match self { CanvasElement::Freehand { path, thickness, stroke_color, } => { use druid::RenderContext; ctx.stroke(&path.kurbo_path, &*stroke_color, *thickness); } } } } #[derive(Clone, druid::Data)] pub struct Canvas { elements: Vector, content_size: druid::Size, } impl Canvas { pub fn new() -> Self { Canvas { elements: vector![], content_size: druid::Size::new(0.0, 0.0), } } pub fn new_with_elements(elements: Vector) -> Self { let mut cv = Canvas::new(); for e in elements { cv.add_element(e); } cv } pub fn add_element(&mut self, element: CanvasElement) { self.content_size = self .content_size .to_rect() .union(element.bounding_box()) .size(); self.elements.push_back(element); } pub fn elements(&self) -> &Vector { &self.elements } /// Find all CanvasElement that intersect with rect pub fn find_intersections(&self, rect: druid::Rect) -> Vec { let mut found_elements = Vec::::new(); for (i, elem) in self.elements.iter().enumerate() { // Check if the element intersects the eraser rect if elem.bounding_box().intersect(rect).area() > 0.0 { if elem.intersects_rect(rect) { found_elements.push(i); } } } found_elements } pub fn content_size(&self) -> druid::Size { self.content_size } } impl Serialize for Path { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use druid::kurbo::PathEl; serializer.collect_seq( self.kurbo_path .iter() .filter_map(|path_el| match path_el { PathEl::MoveTo(pt) => Some(Into::<(f64, f64)>::into(pt)), PathEl::LineTo(pt) => Some(Into::<(f64, f64)>::into(pt)), _ => None, }) .collect::>(), ) } } impl<'de> Deserialize<'de> for Path { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct PathVisitor; impl<'de> Visitor<'de> for PathVisitor { type Value = Path; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "A sequence of 2D points") } fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { use druid::kurbo::BezPath; let mut kurbo_path = BezPath::new(); let mut first_element = true; while let Some(point) = seq.next_element::<(f64, f64)>()? { if first_element { kurbo_path.move_to(point); first_element = false; } else { kurbo_path.line_to(point); } } Ok(Path { kurbo_path }) } } deserializer.deserialize_seq(PathVisitor) } }