// 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,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "druid::Color")]
enum ColorDef {
Rgba32(u32),
}
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(with = "ColorDef")]
stroke_color: druid::Color,
},
}
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);
}
}
}
pub fn draw_selected(&self, ctx: &mut druid::PaintCtx) {
match self {
CanvasElement::Freehand {
path,
thickness,
..
} => {
use druid::RenderContext;
ctx.stroke(&path.kurbo_path, &druid::Color::rgb(10.0, 0.0, 255.0), *thickness * 1.4);
}
}
}
}
#[derive(Clone, druid::Data)]
pub struct Canvas {
elements: Vector,
content_size: druid::Size,
selected_elements: Vector,
}
impl Canvas {
pub fn new() -> Self {
Canvas {
elements: vector![],
content_size: druid::Size::new(0.0, 0.0),
selected_elements: vector![],
}
}
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
}
pub fn selected_elements(&self) -> &Vector {
&self.selected_elements
}
pub fn selected_elements_mut(&mut self) -> &mut Vector {
&mut self.selected_elements
}
}
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)
}
}