// 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 im::Vector; use super::canvas; use super::canvas::{Canvas, CanvasElement}; use super::history::VersionedCanvas; use super::DocumentSnapshot; use druid::kurbo::BezPath; use druid::widget::prelude::*; use druid::{Color, Data, Env, Event}; #[derive(Clone, Data)] pub struct CanvasData { pub current_element: Option, pub elements: VersionedCanvas, } impl CanvasData { pub fn is_drawing(&self) -> bool { self.current_element.is_some() } pub fn perform_undo(&mut self) { if !self.is_drawing() { self.elements.undo(); } } pub fn perform_redo(&mut self) { if !self.is_drawing() { self.elements.redo(); } } pub fn get_document_snapshot(&self) -> DocumentSnapshot { DocumentSnapshot { canvas_elements: self.elements.get().iter().cloned().collect(), } } pub fn set_from_snapshot(&mut self, snapshot: DocumentSnapshot) { self.current_element = None; self.elements = VersionedCanvas::new(Vector::from(snapshot.canvas_elements)); } } pub struct CanvasWidget; impl Widget for CanvasWidget { fn event(&mut self, _ctx: &mut EventCtx, event: &Event, data: &mut CanvasData, _env: &Env) { match event { Event::MouseDown(mouse_event) => { let mut kurbo_path = BezPath::new(); kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y)); data.current_element = Some(CanvasElement::Freehand { path: canvas::Path { kurbo_path }, thickness: 2.0, }); } Event::MouseMove(mouse_event) => { if data.is_drawing() { if let Some(current_element) = data.current_element.as_mut() { current_element .get_path_mut() .unwrap() .kurbo_path .line_to((mouse_event.pos.x, mouse_event.pos.y)); } } } Event::MouseUp(_) => { if data.is_drawing() { if let Some(current_element) = data.current_element.take() { data.elements.update(move |canvas: &mut Canvas| { canvas.push_back(current_element); }); } } } _ => {} } } fn lifecycle( &mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &CanvasData, _env: &Env, ) { } fn update( &mut self, ctx: &mut UpdateCtx, old_data: &CanvasData, data: &CanvasData, _env: &Env, ) { // the current_element is moved to the elements array, no need to repaint if old_data.is_drawing() && !data.is_drawing() { return; } if data.is_drawing() { if let Some(e) = data.current_element.as_ref() { ctx.request_paint_rect(e.bounding_box()); } } else { ctx.request_paint(); } } fn layout( &mut self, _layout_ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &CanvasData, _env: &Env, ) -> Size { // BoxConstraints are passed by the parent widget. // This method can return any Size within those constraints: // bc.constrain(my_size) // // To check if a dimension is infinite or not (e.g. scrolling): // bc.is_width_bounded() / bc.is_height_bounded() bc.max() } // The paint method gets called last, after an event flow. // It goes event -> update -> layout -> paint, and each method can influence the next. // Basically, anything that changes the appearance of a widget causes a paint. fn paint(&mut self, ctx: &mut PaintCtx, data: &CanvasData, _env: &Env) { // (ctx.size() returns the size of the layout rect we're painting in) let size = ctx.size(); let rect = size.to_rect(); // Note: ctx also has a `clear` method, but that clears the whole context, // and we only want to clear this widget's area. ctx.fill(rect, &Color::WHITE); for element in data.elements.get().iter() { element.draw(ctx); } if let Some(element) = &data.current_element { element.draw(ctx); } } }