From 363b9c7fface9a2785244f753a589166cd06f8ca Mon Sep 17 00:00:00 2001 From: Henry Sloan Date: Wed, 14 Jul 2021 03:49:12 -0400 Subject: [PATCH] Implement fill tool (#254) * Implement fill tool * Add fill tool shortcut * Add getters and setters to styles * Make fill tool act on the topmost layer clicked * Refactor fill operation * Refactor and unify selection tolerance * Add mark_as_dirty function * Fix getter names --- client/web/src/components/panels/Document.vue | 2 +- core/document/src/document.rs | 16 +++++++ core/document/src/layers/mod.rs | 2 - core/document/src/layers/style/mod.rs | 18 ++++++++ core/document/src/operation.rs | 5 +++ core/editor/src/consts.rs | 2 + core/editor/src/input/input_mapper.rs | 3 ++ core/editor/src/lib.rs | 1 + core/editor/src/tool/mod.rs | 1 + core/editor/src/tool/tool_message_handler.rs | 3 ++ core/editor/src/tool/tools/fill.rs | 43 +++++++++++++++++++ core/editor/src/tool/tools/mod.rs | 1 + core/editor/src/tool/tools/select.rs | 4 +- 13 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 core/editor/src/tool/tools/fill.rs diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue index 672fd591..a03193e4 100644 --- a/client/web/src/components/panels/Document.vue +++ b/client/web/src/components/panels/Document.vue @@ -112,7 +112,7 @@ - + diff --git a/core/document/src/document.rs b/core/document/src/document.rs index b659aaa9..93b471be 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -227,6 +227,16 @@ impl Document { Ok(()) } + fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { + let mut root = &mut self.root; + root.cache_dirty = true; + for id in path { + root = root.as_folder_mut()?.layer_mut(*id).ok_or(DocumentError::LayerNotFound)?; + root.cache_dirty = true; + } + Ok(()) + } + /// Mutate the document by applying the `operation` to it. If the operation necessitates a /// reaction from the frontend, responses may be returned. pub fn handle_operation(&mut self, operation: Operation) -> Result>, DocumentError> { @@ -361,6 +371,12 @@ impl Document { let path = path.as_slice()[..path.len() - 1].to_vec(); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]) } + Operation::FillLayer { path, color } => { + let layer = self.layer_mut(path).unwrap(); + layer.style.set_fill(layers::style::Fill::new(*color)); + self.mark_as_dirty(path)?; + Some(vec![DocumentResponse::DocumentChanged]) + } }; if !matches!( operation, diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index dca61b32..b9f294a4 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -23,8 +23,6 @@ use crate::LayerId; pub use folder::Folder; use serde::{Deserialize, Serialize}; -pub const SELECTION_TOLERANCE: f64 = 5.0; - pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; diff --git a/core/document/src/layers/style/mod.rs b/core/document/src/layers/style/mod.rs index 2e735403..b109e717 100644 --- a/core/document/src/layers/style/mod.rs +++ b/core/document/src/layers/style/mod.rs @@ -56,6 +56,24 @@ impl PathStyle { pub fn new(stroke: Option, fill: Option) -> Self { Self { stroke, fill } } + pub fn fill(&self) -> Option { + self.fill + } + pub fn stroke(&self) -> Option { + self.stroke + } + pub fn set_fill(&mut self, fill: Fill) { + self.fill = Some(fill); + } + pub fn set_stroke(&mut self, stroke: Stroke) { + self.stroke = Some(stroke); + } + pub fn clear_fill(&mut self) { + self.fill = None; + } + pub fn clear_stroke(&mut self) { + self.stroke = None; + } pub fn render(&self) -> String { format!( "{}{}", diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index eef0cb45..3bd85927 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -1,4 +1,5 @@ use crate::{ + color::Color, layers::{style, Layer}, LayerId, }; @@ -71,4 +72,8 @@ pub enum Operation { ToggleVisibility { path: Vec, }, + FillLayer { + path: Vec, + color: Color, + }, } diff --git a/core/editor/src/consts.rs b/core/editor/src/consts.rs index ce92ef40..bd7265f5 100644 --- a/core/editor/src/consts.rs +++ b/core/editor/src/consts.rs @@ -10,3 +10,5 @@ pub const WHEEL_ZOOM_RATE: f64 = 1. / 600.; pub const MOUSE_ZOOM_RATE: f64 = 1. / 400.; pub const ROTATE_SNAP_INTERVAL: f64 = 15.; + +pub const SELECTION_TOLERANCE: f64 = 5.0; diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index eeee035e..e762cf79 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -169,7 +169,10 @@ impl Default for Mapping { entry! {action=PenMessage::Confirm, key_down=Rmb}, entry! {action=PenMessage::Confirm, key_down=KeyEscape}, entry! {action=PenMessage::Confirm, key_down=KeyEnter}, + // Fill + entry! {action=FillMessage::MouseDown, key_down=Lmb}, // Tool Actions + entry! {action=ToolMessage::SelectTool(ToolType::Fill), key_down=KeyF}, entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM}, entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE}, entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV}, diff --git a/core/editor/src/lib.rs b/core/editor/src/lib.rs index 8ba3eaec..fa6351a9 100644 --- a/core/editor/src/lib.rs +++ b/core/editor/src/lib.rs @@ -60,6 +60,7 @@ pub mod message_prelude { pub use super::tool::tool_messages::*; pub use super::tool::tools::crop::{CropMessage, CropMessageDiscriminant}; pub use super::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant}; + pub use super::tool::tools::fill::{FillMessage, FillMessageDiscriminant}; pub use super::tool::tools::line::{LineMessage, LineMessageDiscriminant}; pub use super::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant}; pub use super::tool::tools::path::{PathMessage, PathMessageDiscriminant}; diff --git a/core/editor/src/tool/mod.rs b/core/editor/src/tool/mod.rs index f334cf04..c0976906 100644 --- a/core/editor/src/tool/mod.rs +++ b/core/editor/src/tool/mod.rs @@ -83,6 +83,7 @@ impl Default for ToolFsmState { Line => line::Line, Shape => shape::Shape, Ellipse => ellipse::Ellipse, + Fill => fill::Fill, }, }, document_tool_data: DocumentToolData { diff --git a/core/editor/src/tool/tool_message_handler.rs b/core/editor/src/tool/tool_message_handler.rs index a5f1c89e..69e33026 100644 --- a/core/editor/src/tool/tool_message_handler.rs +++ b/core/editor/src/tool/tool_message_handler.rs @@ -17,6 +17,8 @@ pub enum ToolMessage { SwapColors, ResetColors, #[child] + Fill(FillMessage), + #[child] Rectangle(RectangleMessage), #[child] Ellipse(EllipseMessage), @@ -89,6 +91,7 @@ impl MessageHandler for ToolMes } message => { let tool_type = match message { + Fill(_) => ToolType::Fill, Rectangle(_) => ToolType::Rectangle, Ellipse(_) => ToolType::Ellipse, Shape(_) => ToolType::Shape, diff --git a/core/editor/src/tool/tools/fill.rs b/core/editor/src/tool/tools/fill.rs new file mode 100644 index 00000000..91a1d2f1 --- /dev/null +++ b/core/editor/src/tool/tools/fill.rs @@ -0,0 +1,43 @@ +use crate::consts::SELECTION_TOLERANCE; +use crate::message_prelude::*; +use crate::tool::ToolActionHandlerData; +use document_core::Operation; +use glam::DVec2; + +#[derive(Default)] +pub struct Fill; + +#[impl_message(Message, ToolMessage, Fill)] +#[derive(PartialEq, Clone, Debug)] +pub enum FillMessage { + MouseDown, +} + +impl<'a> MessageHandler> for Fill { + fn process_action(&mut self, _action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + let mouse_pos = data.2.mouse.position; + let (x, y) = (mouse_pos.x as f64, mouse_pos.y as f64); + let (point_1, point_2) = ( + DVec2::new(x - SELECTION_TOLERANCE, y - SELECTION_TOLERANCE), + DVec2::new(x + SELECTION_TOLERANCE, y + SELECTION_TOLERANCE), + ); + + let quad = [ + DVec2::new(point_1.x, point_1.y), + DVec2::new(point_2.x, point_1.y), + DVec2::new(point_2.x, point_2.y), + DVec2::new(point_1.x, point_2.y), + ]; + + if let Some(path) = data.0.intersects_quad_root(quad).last() { + responses.push_back( + Operation::FillLayer { + path: path.to_vec(), + color: data.1.primary_color, + } + .into(), + ); + } + } + advertise_actions!(FillMessageDiscriminant; MouseDown); +} diff --git a/core/editor/src/tool/tools/mod.rs b/core/editor/src/tool/tools/mod.rs index f57a31c1..e60a0892 100644 --- a/core/editor/src/tool/tools/mod.rs +++ b/core/editor/src/tool/tools/mod.rs @@ -1,5 +1,6 @@ // already implemented pub mod ellipse; +pub mod fill; pub mod line; pub mod pen; pub mod rectangle; diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index 162dcd8b..2234a41f 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -1,13 +1,13 @@ use document_core::color::Color; +use document_core::layers::style; use document_core::layers::style::Fill; use document_core::layers::style::Stroke; -use document_core::layers::{style, SELECTION_TOLERANCE}; use document_core::Operation; use glam::{DAffine2, DVec2}; use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; -use crate::{message_prelude::*, SvgDocument}; +use crate::{consts::SELECTION_TOLERANCE, message_prelude::*, SvgDocument}; #[derive(Default)] pub struct Select {