From e40727e914674b57470cc3eb00c8378d85b564a8 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Fri, 23 Apr 2021 00:12:24 +0800 Subject: [PATCH] Implement Pen Tool (#79) --- client/web/src/components/panels/Document.vue | 4 +- client/web/wasm/src/wrappers.rs | 2 + core/document/src/document.rs | 8 ++ core/document/src/layers/mod.rs | 5 ++ core/document/src/layers/polyline.rs | 24 +++++ core/document/src/layers/style/mod.rs | 12 ++- core/document/src/operation.rs | 6 ++ core/editor/src/dispatcher/events.rs | 2 + core/editor/src/tools/pen.rs | 88 ++++++++++++++++++- 9 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 core/document/src/layers/polyline.rs diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue index e4c21a57..8ce15f84 100644 --- a/client/web/src/components/panels/Document.vue +++ b/client/web/src/components/panels/Document.vue @@ -72,10 +72,10 @@ - + - + diff --git a/client/web/wasm/src/wrappers.rs b/client/web/wasm/src/wrappers.rs index b6fb8401..6a7a5c64 100644 --- a/client/web/wasm/src/wrappers.rs +++ b/client/web/wasm/src/wrappers.rs @@ -80,6 +80,7 @@ pub fn translate_key(name: &str) -> events::Key { "e" => K::KeyE, "v" => K::KeyV, "l" => K::KeyL, + "p" => K::KeyP, "r" => K::KeyR, "m" => K::KeyM, "x" => K::KeyX, @@ -95,6 +96,7 @@ pub fn translate_key(name: &str) -> events::Key { "7" => K::Key7, "8" => K::Key8, "9" => K::Key9, + "Enter" => K::KeyEnter, _ => K::UnknownKey, } } diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 98e29d13..0969e65c 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,3 +1,5 @@ +use layers::PolyLine; + use crate::{ layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, Rect, Shape}, DocumentError, LayerId, Operation, @@ -178,6 +180,12 @@ impl Document { update_frontend(self.render(&mut vec![])); } + Operation::AddPen { path, insert_index, points, style } => { + let points: Vec = points.into_iter().map(|it| it.into()).collect(); + let polyline = PolyLine::new(points, style); + self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline)), insert_index)?; + update_frontend(self.render(&mut vec![])); + } Operation::AddShape { path, insert_index, diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 3700c639..4fd5a76b 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -9,6 +9,9 @@ pub use line::Line; pub mod rect; pub use rect::Rect; +pub mod polyline; +pub use polyline::PolyLine; + pub mod shape; pub use shape::Shape; @@ -25,6 +28,7 @@ pub enum LayerDataTypes { Circle(Circle), Rect(Rect), Line(Line), + PolyLine(PolyLine), Shape(Shape), } @@ -35,6 +39,7 @@ impl LayerDataTypes { Self::Circle(c) => c.render(), Self::Rect(r) => r.render(), Self::Line(l) => l.render(), + Self::PolyLine(pl) => pl.render(), Self::Shape(s) => s.render(), } } diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs new file mode 100644 index 00000000..fc67af88 --- /dev/null +++ b/core/document/src/layers/polyline.rs @@ -0,0 +1,24 @@ +use super::style; +use super::LayerData; + +#[derive(Debug, Clone, PartialEq)] +pub struct PolyLine { + points: Vec, + style: style::PathStyle, +} + +impl PolyLine { + pub fn new(points: Vec>, style: style::PathStyle) -> PolyLine { + PolyLine { + points: points.into_iter().map(|it| it.into()).collect(), + style, + } + } +} + +impl LayerData for PolyLine { + fn render(&self) -> String { + let points = self.points.iter().map(|p| format!("{},{}", p.x, p.y)).collect::>().join(" "); + format!(r#""#, points, self.style.render()) + } +} diff --git a/core/document/src/layers/style/mod.rs b/core/document/src/layers/style/mod.rs index 7def803e..958bc219 100644 --- a/core/document/src/layers/style/mod.rs +++ b/core/document/src/layers/style/mod.rs @@ -3,14 +3,20 @@ use crate::color::Color; #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Fill { - color: Color, + color: Option, } impl Fill { pub fn new(color: Color) -> Self { - Self { color } + Self { color: Some(color) } + } + pub fn none() -> Self { + Self { color: None } } pub fn render(&self) -> String { - format!("fill: #{};", self.color.as_hex()) + match self.color { + Some(c) => format!("fill: #{};", c.as_hex()), + None => format!("fill: none;"), + } } } diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index fcfe2b69..7af1f740 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -28,6 +28,12 @@ pub enum Operation { y1: f64, style: style::PathStyle, }, + AddPen { + path: Vec, + insert_index: isize, + points: Vec<(f64, f64)>, + style: style::PathStyle, + }, AddShape { path: Vec, insert_index: isize, diff --git a/core/editor/src/dispatcher/events.rs b/core/editor/src/dispatcher/events.rs index 4b4d3c5f..a2c8e60d 100644 --- a/core/editor/src/dispatcher/events.rs +++ b/core/editor/src/dispatcher/events.rs @@ -112,10 +112,12 @@ pub enum Key { KeyM, KeyE, KeyL, + KeyP, KeyV, KeyX, KeyZ, KeyY, + KeyEnter, Key0, Key1, Key2, diff --git a/core/editor/src/tools/pen.rs b/core/editor/src/tools/pen.rs index 812e881a..5d72a478 100644 --- a/core/editor/src/tools/pen.rs +++ b/core/editor/src/tools/pen.rs @@ -1,15 +1,97 @@ use crate::events::{Event, Response}; -use crate::tools::Tool; +use crate::events::{Key, MouseKeys, ViewportPosition}; +use crate::tools::{Fsm, Tool}; use crate::Document; + +use document_core::layers::style; use document_core::Operation; use super::DocumentToolData; #[derive(Default)] -pub struct Pen; +pub struct Pen { + fsm_state: PenToolFsmState, + data: PenToolData, +} impl Tool for Pen { fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec, Vec) { - todo!("{}::handle_input {:?} {:?} {:?}", module_path!(), event, document, tool_data) + let mut responses = Vec::new(); + let mut operations = Vec::new(); + self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations); + + (responses, operations) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PenToolFsmState { + Ready, + LmbDown, +} + +impl Default for PenToolFsmState { + fn default() -> Self { + PenToolFsmState::Ready + } +} +#[derive(Clone, Debug, Default)] +struct PenToolData { + points: Vec, +} + +impl Fsm for PenToolFsmState { + type ToolData = PenToolData; + + fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec, operations: &mut Vec) -> Self { + let stroke = style::Stroke::new(tool_data.primary_color, 5.); + let fill = style::Fill::none(); + let style = style::PathStyle::new(Some(stroke), Some(fill)); + + match (self, event) { + (PenToolFsmState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => { + operations.push(Operation::MountWorkingFolder { path: vec![] }); + data.points.push(mouse_state.position); + PenToolFsmState::LmbDown + } + (PenToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => { + if let Some(id) = document.root.list_layers().last() { + operations.push(Operation::DeleteLayer { path: vec![*id] }) + } + PenToolFsmState::Ready + } + // TODO - Check for left mouse button + (PenToolFsmState::LmbDown, Event::MouseDown(mouse_state)) => { + data.points.push(mouse_state.position); + PenToolFsmState::LmbDown + } + (PenToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => { + let mut points: Vec<_> = data.points.iter().map(|p| (p.x as f64, p.y as f64)).collect(); + points.push((mouse_state.x as f64, mouse_state.y as f64)); + + operations.push(Operation::ClearWorkingFolder); + operations.push(Operation::AddPen { + path: vec![], + insert_index: -1, + points, + style, + }); + PenToolFsmState::LmbDown + } + (PenToolFsmState::LmbDown, Event::KeyDown(Key::KeyEnter)) => { + let points = data.points.drain(..).map(|p| (p.x as f64, p.y as f64)).collect(); + operations.push(Operation::ClearWorkingFolder); + operations.push(Operation::AddPen { + path: vec![], + insert_index: -1, + points, + style, + }); + operations.push(Operation::CommitTransaction); + PenToolFsmState::Ready + } + + _ => self, + } } }