// 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 crate::tool::{CanvasToolParams, CanvasToolType}; use crate::canvas::{Canvas, CanvasElement}; use crate::history::VersionedCanvas; use druid::kurbo::BezPath; use druid::{Color, Data, Env, Event, EventCtx, MouseButton, MouseEvent, PaintCtx, RenderContext}; use im::Vector; #[derive(Clone, Data)] pub enum CanvasToolState { Idle, DrawingFreehand { pen_params: CanvasToolParams, current_path: CanvasElement, }, Erasing, SelectingRect { origin: druid::Point, current_rect: druid::Rect, } } #[derive(Clone, Data)] pub struct CanvasToolCtx { initial_params: CanvasToolParams, state: CanvasToolState, } impl CanvasToolParams { pub fn tool_type(&self) -> CanvasToolType { match self { CanvasToolParams::Pen { .. } => CanvasToolType::Pen, CanvasToolParams::Eraser => CanvasToolType::Eraser, CanvasToolParams::RectangularSelection => CanvasToolType::RectangularSelection, } } } fn pressed(mouse_event: &MouseEvent) -> bool { mouse_event.buttons.contains(MouseButton::Left) || mouse_event.button == MouseButton::Left } impl CanvasToolCtx { pub fn new(params: CanvasToolParams) -> Self { CanvasToolCtx { initial_params: params, state: CanvasToolState::Idle, } } pub fn handle_event( &mut self, ctx: &EventCtx, event: &Event, mut vcanvas: &mut VersionedCanvas, env: &Env, ) { match self.initial_params.tool_type() { CanvasToolType::Pen => self.handle_pen_event(&ctx, &event, &mut vcanvas, &env), CanvasToolType::Eraser => self.handle_erase_event(&ctx, &event, &mut vcanvas, &env), CanvasToolType::RectangularSelection => self.handle_rectangular_select_event(&ctx, &event, &mut vcanvas, &env), } } pub fn handle_erase_event( &mut self, _ctx: &EventCtx, event: &Event, vcanvas: &mut VersionedCanvas, _env: &Env, ) { match (&mut self.state, event) { (CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => { self.state = CanvasToolState::Erasing; } (CanvasToolState::Erasing, Event::MouseMove(mouse_event)) if pressed(mouse_event) => { let eraser_rect = druid::Rect::from_center_size(mouse_event.pos, (5.0, 5.0)); let old_elements = vcanvas.get().elements(); let mut new_elements = old_elements.clone(); new_elements.retain(|elem| { // Check if the element intersects the eraser rect if elem.bounding_box().intersect(eraser_rect).area() > 0.0 { if elem.intersects_rect(eraser_rect) { return false; } } return true; }); if new_elements.len() != old_elements.len() { vcanvas.update(|canvas: &mut Canvas| { *canvas = Canvas::new_with_elements(new_elements); }); } } (CanvasToolState::Erasing, Event::MouseUp(mouse_event)) if pressed(mouse_event) => { self.state = CanvasToolState::Idle; } _ => {} } } pub fn handle_pen_event( &mut self, _ctx: &EventCtx, event: &Event, vcanvas: &mut VersionedCanvas, _env: &Env, ) { match (&mut self.state, event) { (CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => { let mut kurbo_path = BezPath::new(); kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y)); if let CanvasToolParams::Pen { thickness, color } = &self.initial_params { self.state = CanvasToolState::DrawingFreehand { pen_params: self.initial_params.clone(), current_path: CanvasElement::Freehand { path: crate::canvas::Path { kurbo_path }, thickness: *thickness, stroke_color: color.clone(), }, }; } } ( CanvasToolState::DrawingFreehand { ref mut current_path, .. }, Event::MouseMove(mouse_event), ) if pressed(mouse_event) => { if let CanvasElement::Freehand { ref mut path, .. } = current_path { path.kurbo_path .line_to((mouse_event.pos.x, mouse_event.pos.y)); } } (CanvasToolState::DrawingFreehand { .. }, Event::MouseUp(mouse_event)) if pressed(mouse_event) => { vcanvas.update(move |canvas: &mut Canvas| { let current_state = std::mem::replace(&mut self.state, CanvasToolState::Idle); if let CanvasToolState::DrawingFreehand { current_path, .. } = current_state { if let CanvasElement::Freehand { mut path, mut thickness, stroke_color, } = current_path { canvas.add_element(CanvasElement::Freehand { path, thickness, stroke_color, }); } } }); } _ => {} } } pub fn handle_rectangular_select_event( &mut self, _ctx: &EventCtx, event: &Event, vcanvas: &mut VersionedCanvas, _env: &Env, ) { match (&mut self.state, event) { (CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => { self.state = CanvasToolState::SelectingRect { origin: mouse_event.pos, current_rect: druid::Rect::from_origin_size(mouse_event.pos, (0.0, 0.0)) }; } (CanvasToolState::SelectingRect{ ref mut current_rect, ref origin }, Event::MouseMove(mouse_event)) if pressed(mouse_event) => { *current_rect = druid::Rect::from_origin_size(*origin, (0.0, 0.0)) .union_pt(mouse_event.pos); } (CanvasToolState::SelectingRect{ ref mut current_rect, ref origin }, Event::MouseUp(mouse_event)) if pressed(mouse_event) => { *current_rect = druid::Rect::from_origin_size(*origin, (0.0, 0.0)) .union_pt(mouse_event.pos); let elements = vcanvas.get().elements(); let selected_elements_idx = elements.iter().enumerate() .filter_map(|(i, elem)| { let bbox = elem.bounding_box(); let p0 = druid::Point::new(bbox.x0, bbox.y0); let p1 = druid::Point::new(bbox.x1, bbox.y1); if current_rect.contains(p0) && current_rect.contains(p1) { return Some(i); } None }).collect::>(); dbg!(selected_elements_idx); self.state = CanvasToolState::Idle; } _ => {} } } pub fn needs_repaint(&self) -> bool { true } pub fn paint(&self, ctx: &mut PaintCtx, _env: &Env) { match &self.state { CanvasToolState::DrawingFreehand { current_path, .. } => current_path.draw(ctx), CanvasToolState::SelectingRect { current_rect, .. } => ctx.stroke(current_rect, &Color::BLACK, 0.5), _ => {} } } }