From 959e790cdf7d1e831fbff0826d2f0a1e4ac43457 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Sun, 26 Mar 2023 08:03:51 +0100 Subject: [PATCH] Migrate vector data and tools to use nodes (#1065) * Add rendering to vector nodes * Add line, shape, rectange and freehand tool * Fix transforms, strokes and fills * Migrate spline tool * Remove blank lines * Fix test * Fix fill in properties * Select layers when filling * Properties panel transform around pivot * Fix select tool outlines * Select tool modifies node graph pivot * Add the pivot assist to the properties * Improve setting non existant fill UX * Cleanup hash function * Path and pen tools * Bug fixes * Disable boolean ops * Fix default handle smoothing on ellipses * Fix test and warnings --------- Co-authored-by: Keavon Chambers --- document-legacy/src/document.rs | 45 +- document-legacy/src/intersection.rs | 37 + document-legacy/src/layers/layer_info.rs | 20 +- document-legacy/src/layers/nodegraph_layer.rs | 34 +- document-legacy/src/operation.rs | 4 + .../portfolio/document/document_message.rs | 14 +- .../document/document_message_handler.rs | 86 +- .../node_graph/graph_operation_message.rs | 44 + .../graph_operation_message_handler.rs | 262 ++++++ .../transform_utils.rs | 301 +++++++ .../portfolio/document/node_graph/mod.rs | 8 + .../node_graph/node_graph_message_handler.rs | 20 +- .../document_node_types.rs | 22 +- .../node_properties.rs | 55 +- .../properties_panel_message_handler.rs | 8 +- .../document/utility_types/transformation.rs | 56 +- editor/src/messages/prelude.rs | 16 + .../graph_modification_utils.rs | 25 + .../messages/tool/common_functionality/mod.rs | 1 + .../common_functionality/overlay_renderer.rs | 191 +++-- .../tool/common_functionality/path_outline.rs | 2 + .../tool/common_functionality/pivot.rs | 4 +- .../tool/common_functionality/resize.rs | 8 +- .../tool/common_functionality/shape_editor.rs | 652 +++++++------- .../tool/common_functionality/snapping.rs | 53 +- .../src/messages/tool/tool_message_handler.rs | 70 +- .../tool/tool_messages/artboard_tool.rs | 6 +- .../tool/tool_messages/ellipse_tool.rs | 55 +- .../tool/tool_messages/eyedropper_tool.rs | 6 +- .../messages/tool/tool_messages/fill_tool.rs | 22 +- .../tool/tool_messages/freehand_tool.rs | 38 +- .../tool/tool_messages/gradient_tool.rs | 20 +- .../tool/tool_messages/imaginate_tool.rs | 6 +- .../messages/tool/tool_messages/line_tool.rs | 46 +- .../tool/tool_messages/navigate_tool.rs | 6 +- .../tool_messages/node_graph_frame_tool.rs | 6 +- .../messages/tool/tool_messages/path_tool.rs | 135 +-- .../messages/tool/tool_messages/pen_tool.rs | 804 +++++++++--------- .../tool/tool_messages/rectangle_tool.rs | 41 +- .../tool/tool_messages/select_tool.rs | 48 +- .../messages/tool/tool_messages/shape_tool.rs | 39 +- .../tool/tool_messages/spline_tool.rs | 50 +- .../messages/tool/tool_messages/text_tool.rs | 19 +- .../transform_layer_message_handler.rs | 30 +- editor/src/messages/tool/utility_types.rs | 51 +- editor/src/node_graph_executor.rs | 31 +- libraries/bezier-rs/src/bezier/core.rs | 10 + libraries/bezier-rs/src/bezier/solvers.rs | 101 ++- libraries/bezier-rs/src/bezier/transform.rs | 12 +- libraries/bezier-rs/src/subpath/core.rs | 24 +- .../bezier-rs/src/subpath/manipulators.rs | 30 + libraries/bezier-rs/src/subpath/solvers.rs | 12 + node-graph/gcore/src/transform.rs | 18 +- node-graph/gcore/src/uuid.rs | 3 +- .../gcore/src/vector/generator_nodes.rs | 56 +- node-graph/gcore/src/vector/mod.rs | 2 +- node-graph/gcore/src/vector/subpath.rs | 17 + node-graph/gcore/src/vector/vector_data.rs | 137 ++- node-graph/gcore/src/vector/vector_nodes.rs | 5 +- node-graph/graph-craft/src/document.rs | 75 +- node-graph/graph-craft/src/document/value.rs | 154 +--- node-graph/graph-craft/src/proto.rs | 12 +- node-graph/gstd/src/any.rs | 4 +- .../interpreted-executor/src/node_registry.rs | 22 +- 64 files changed, 2639 insertions(+), 1552 deletions(-) create mode 100644 editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs create mode 100644 editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs create mode 100644 editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs create mode 100644 editor/src/messages/tool/common_functionality/graph_modification_utils.rs diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 2d41670b81..143a828748 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -8,8 +8,6 @@ use crate::layers::style::RenderData; use crate::layers::text_layer::{Font, TextLayer}; use crate::{DocumentError, DocumentResponse, Operation}; -use graphene_std::vector::subpath::Subpath; - use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; use std::cell::RefCell; @@ -177,43 +175,6 @@ impl Document { Ok(shapes) } - /// Return a copy of all [Subpath]s currently in the document. - pub fn all_subpaths(&self) -> Vec { - self.root.iter().flat_map(|layer| layer.as_subpath_copy()).collect::>() - } - - /// Returns references to all [Subpath]s currently in the document. - pub fn all_subpaths_ref(&self) -> Vec<&Subpath> { - self.root.iter().flat_map(|layer| layer.as_subpath()).collect::>() - } - - /// Returns a reference to the requested [Subpath] by providing a path to its owner layer. - pub fn subpath_ref<'a>(&'a self, path: &[LayerId]) -> Option<&'a Subpath> { - self.layer(path).ok()?.as_subpath() - } - - /// Returns a mutable reference of the requested [Subpath] by providing a path to its owner layer. - pub fn subpath_mut<'a>(&'a mut self, path: &'a [LayerId]) -> Option<&'a mut Subpath> { - self.layer_mut(path).ok()?.as_subpath_mut() - } - - /// Set a [Subpath] at the specified path. - pub fn set_subpath(&mut self, path: &[LayerId], shape: Subpath) { - let layer = self.layer_mut(path); - if let Ok(layer) = layer { - if let LayerDataType::Shape(shape_layer) = &mut layer.data { - shape_layer.shape = shape; - // Is this needed? - layer.cache_dirty = true; - } - } - } - - /// Set [Subpath]s for multiple paths at once. - pub fn set_subpaths<'a>(&'a mut self, paths: impl Iterator, shapes: Vec) { - paths.zip(shapes).for_each(|(path, shape)| self.set_subpath(path, shape)); - } - pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator) -> &'a [LayerId] { layers.reduce(|a, b| &a[..a.iter().zip(b.iter()).take_while(|&(a, b)| a == b).count()]).unwrap_or_default() } @@ -865,6 +826,12 @@ impl Document { } Some(vec![DocumentChanged, LayerChanged { path }]) } + Operation::SetVectorData { path, vector_data } => { + if let LayerDataType::NodeGraphFrame(graph) = &mut self.layer_mut(&path)?.data { + graph.vector_data = Some(vector_data); + } + Some(Vec::new()) + } Operation::InsertManipulatorGroup { layer_path, manipulator_group, diff --git a/document-legacy/src/intersection.rs b/document-legacy/src/intersection.rs index 3e26c6ee96..50ac44f004 100644 --- a/document-legacy/src/intersection.rs +++ b/document-legacy/src/intersection.rs @@ -1,6 +1,7 @@ use crate::boolean_ops::{split_path_seg, subdivide_path_seg}; use crate::consts::{F64LOOSE, F64PRECISE}; +use graphene_core::uuid::ManipulatorGroupId; use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DMat2, DVec2}; @@ -19,6 +20,13 @@ impl Quad { Self([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y]) } + /// Get all the edges in the quad. + pub fn lines_glam(&self) -> impl Iterator + '_ { + [[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]] + .into_iter() + .map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end)) + } + /// Get all the edges in the quad. pub fn lines(&self) -> [Line; 4] { [ @@ -101,6 +109,35 @@ pub fn intersect_quad_bez_path(quad: Quad, shape: &BezPath, filled: bool) -> boo get_arbitrary_point_on_path(&shape).map(|shape_point| quad.path().contains(shape_point)).unwrap_or_default() } +pub fn intersect_quad_subpath(quad: Quad, subpath: &bezier_rs::Subpath, close_subpath: bool) -> bool { + let mut subpath = subpath.clone(); + + // For close_subpath shapes act like shape was closed even if it isn't + if close_subpath && !subpath.closed() { + subpath.set_closed(true); + } + + // Check if outlines intersect + if subpath + .iter() + .any(|path_segment| quad.lines_glam().any(|line| !path_segment.intersections(&line, None, None).is_empty())) + { + return true; + } + // Check if selection is entirely within the shape + if close_subpath && subpath.contains_point(quad.center()) { + return true; + } + + // Check if shape is entirely within selection + subpath + .manipulator_groups() + .first() + .map(|group| group.anchor) + .map(|shape_point| quad.path().contains(to_point(shape_point))) + .unwrap_or_default() +} + /// Returns a point on `path`. /// This function will usually return the first point from the path's first segment, but callers should not rely on this behavior. pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { diff --git a/document-legacy/src/layers/layer_info.rs b/document-legacy/src/layers/layer_info.rs index 62441f7779..18344ba38b 100644 --- a/document-legacy/src/layers/layer_info.rs +++ b/document-legacy/src/layers/layer_info.rs @@ -8,6 +8,7 @@ use crate::intersection::Quad; use crate::DocumentError; use crate::LayerId; +use graphene_core::vector::VectorData; use graphene_std::vector::subpath::Subpath; use core::fmt; @@ -437,16 +438,9 @@ impl Layer { } } - pub fn as_subpath(&self) -> Option<&Subpath> { + pub fn as_vector_data(&self) -> Option<&VectorData> { match &self.data { - LayerDataType::Shape(s) => Some(&s.shape), - _ => None, - } - } - - pub fn as_subpath_copy(&self) -> Option { - match &self.data { - LayerDataType::Shape(s) => Some(s.shape.clone()), + LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(vector_data), _ => None, } } @@ -503,10 +497,18 @@ impl Layer { } } + pub fn as_graph_frame(&self) -> Result<&NodeGraphFrameLayer, DocumentError> { + match &self.data { + LayerDataType::NodeGraphFrame(frame) => Ok(frame), + _ => Err(DocumentError::NotNodeGraph), + } + } + pub fn style(&self) -> Result<&PathStyle, DocumentError> { match &self.data { LayerDataType::Shape(s) => Ok(&s.style), LayerDataType::Text(t) => Ok(&t.path_style), + LayerDataType::NodeGraphFrame(t) => t.vector_data.as_ref().map(|vector| &vector.style).ok_or(DocumentError::NotShape), _ => Err(DocumentError::NotShape), } } diff --git a/document-legacy/src/layers/nodegraph_layer.rs b/document-legacy/src/layers/nodegraph_layer.rs index f8b8ea8472..7d8405b973 100644 --- a/document-legacy/src/layers/nodegraph_layer.rs +++ b/document-legacy/src/layers/nodegraph_layer.rs @@ -1,10 +1,11 @@ use super::base64_serde; use super::layer_info::LayerData; use super::style::{RenderData, ViewMode}; -use crate::intersection::{intersect_quad_bez_path, Quad}; +use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad}; use crate::LayerId; use glam::{DAffine2, DMat2, DVec2}; +use graphene_core::vector::VectorData; use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -23,6 +24,7 @@ pub struct NodeGraphFrameLayer { #[serde(skip)] pub dimensions: DVec2, pub image_data: Option, + pub vector_data: Option, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, specta::Type)] @@ -33,7 +35,7 @@ pub struct ImageData { } impl LayerData for NodeGraphFrameLayer { - fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { + fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { let transform = self.transform(transforms, render_data.view_mode); let inverse = transform.inverse(); @@ -56,7 +58,21 @@ impl LayerData for NodeGraphFrameLayer { .enumerate() .fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," })); - if let Some(blob_url) = &self.blob_url { + // Render any paths if they exist + if let Some(vector_data) = &self.vector_data { + let layer_bounds = vector_data.bounding_box().unwrap_or_default(); + let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default(); + + let _ = write!(svg, ""); + } else if let Some(blob_url) = &self.blob_url { + // Render the image if it exists let _ = write!( svg, r#""#, @@ -66,6 +82,7 @@ impl LayerData for NodeGraphFrameLayer { matrix ); } else { + // Render a dotted blue outline if there is no image or vector data let _ = write!( svg, r#""#, @@ -81,6 +98,10 @@ impl LayerData for NodeGraphFrameLayer { } fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> { + if let Some(vector_data) = &self.vector_data { + return vector_data.bounding_box_with_transform(transform); + } + let mut path = self.bounds(); if transform.matrix2 == DMat2::ZERO { @@ -93,7 +114,12 @@ impl LayerData for NodeGraphFrameLayer { } fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, _render_data: &RenderData) { - if intersect_quad_bez_path(quad, &self.bounds(), true) { + if let Some(vector_data) = &self.vector_data { + let filled_style = vector_data.style.fill().is_some(); + if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) { + intersections.push(path.clone()); + } + } else if intersect_quad_bez_path(quad, &self.bounds(), true) { intersections.push(path.clone()); } } diff --git a/document-legacy/src/operation.rs b/document-legacy/src/operation.rs index dbf84b7763..0039f8b390 100644 --- a/document-legacy/src/operation.rs +++ b/document-legacy/src/operation.rs @@ -187,6 +187,10 @@ pub enum Operation { path: Vec, subpath: Subpath, }, + SetVectorData { + path: Vec, + vector_data: graphene_core::vector::VectorData, + }, InsertManipulatorGroup { layer_path: Vec, manipulator_group: ManipulatorGroup, diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index d1b03c8130..7b5fdac5c7 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -36,6 +36,9 @@ pub enum DocumentMessage { #[remain::unsorted] #[child] NodeGraph(NodeGraphMessage), + #[remain::unsorted] + #[child] + GraphOperation(GraphOperationMessage), // Messages AbortTransaction, @@ -62,9 +65,7 @@ pub enum DocumentMessage { layer_path: Vec, }, DeleteSelectedLayers, - DeleteSelectedManipulatorPoints, DeselectAllLayers, - DeselectAllManipulatorPoints, DirtyRenderDocument, DirtyRenderDocumentInOutlineView, DocumentHistoryBackward, @@ -93,11 +94,6 @@ pub enum DocumentMessage { insert_index: isize, reverse_index: bool, }, - MoveSelectedManipulatorPoints { - layer_path: Vec, - delta: (f64, f64), - mirror_distance: bool, - }, NodeGraphFrameClear { layer_path: Vec, node_id: NodeId, @@ -193,10 +189,6 @@ pub enum DocumentMessage { ToggleLayerVisibility { layer_path: Vec, }, - ToggleSelectedHandleMirroring { - layer_path: Vec, - toggle_angle: bool, - }, Undo, UndoFinished, UngroupLayers { diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ce8d287280..6a5142ed20 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -33,7 +33,6 @@ use document_legacy::layers::text_layer::Font; use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; use graph_craft::document::NodeId; use graphene_core::raster::{Color, ImageFrame}; -use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -207,6 +206,8 @@ impl MessageHandler GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)), // Messages AbortTransaction => { @@ -252,13 +253,11 @@ impl MessageHandler lerp(&bbox), }; let translation = (aggregated - center) * axis; - responses.push_back( - DocumentOperation::TransformLayerInViewport { - path: path.to_vec(), - transform: DAffine2::from_translation(translation).to_cols_array(), - } - .into(), - ); + responses.add(GraphOperationMessage::TransformChange { + layer: path.to_vec(), + transform: DAffine2::from_translation(translation), + transform_in: TransformIn::Viewport, + }); } responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } @@ -323,25 +322,10 @@ impl MessageHandler { - responses.push_back(StartTransaction.into()); - - responses.push_front( - DocumentOperation::DeleteSelectedManipulatorPoints { - layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(), - } - .into(), - ); - } DeselectAllLayers => { responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into()); self.layer_range_selection_reference.clear(); } - DeselectAllManipulatorPoints => { - for layer_path in self.selected_layers_without_children() { - responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into()); - } - } DirtyRenderDocument => { // Mark all non-overlay caches as dirty DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root); @@ -409,14 +393,11 @@ impl MessageHandler { - if let Ok(_layer) = self.document_legacy.layer(&layer_path) { - responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, mirror_distance }.into()); - } - } NodeGraphFrameClear { layer_path, node_id, @@ -577,7 +553,7 @@ impl MessageHandler { - responses.push_back(DocumentOperation::SetSelectedHandleMirroring { layer_path, toggle_angle }.into()); - } Undo => { self.undo_in_progress = true; responses.push_back(BroadcastEvent::ToolAbort.into()); @@ -1250,22 +1222,6 @@ impl DocumentMessageHandler { }) } - /// Returns a copy of all the currently selected [Subpath]s. - pub fn selected_subpaths(&self) -> Vec { - self.selected_visible_layers() - .flat_map(|layer| self.document_legacy.layer(layer)) - .flat_map(|layer| layer.as_subpath_copy()) - .collect::>() - } - - /// Returns references to all the currently selected [Subpath]s. - pub fn selected_subpaths_ref(&self) -> Vec<&Subpath> { - self.selected_visible_layers() - .flat_map(|layer| self.document_legacy.layer(layer)) - .flat_map(|layer| layer.as_subpath()) - .collect::>() - } - /// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths. pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec>>, ignore_artboard: Option, render_data: &'a RenderData) -> impl Iterator + 'a { self.visible_layers() diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs new file mode 100644 index 0000000000..836c8867cd --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs @@ -0,0 +1,44 @@ +use crate::messages::prelude::*; + +use glam::{DAffine2, DVec2}; +use graphene_core::uuid::ManipulatorGroupId; +use graphene_core::vector::style::{Fill, Stroke}; +use graphene_core::vector::ManipulatorPointId; + +pub type LayerIdentifier = Vec; + +#[impl_message(Message, DocumentMessage, GraphOperation)] +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum GraphOperationMessage { + FillSet { layer: LayerIdentifier, fill: Fill }, + + StrokeSet { layer: LayerIdentifier, stroke: Stroke }, + + TransformChange { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn }, + TransformSet { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn }, + TransformSetPivot { layer: LayerIdentifier, pivot: DVec2 }, + + Vector { layer: LayerIdentifier, modification: VectorDataModification }, +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum TransformIn { + Local, + Scope { scope: DAffine2 }, + Viewport, +} + +type ManipulatorGroup = bezier_rs::ManipulatorGroup; + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum VectorDataModification { + AddEndManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup }, + AddManipulatorGroup { manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId }, + AddStartManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup }, + RemoveManipulatorGroup { id: ManipulatorGroupId }, + RemoveManipulatorPoint { point: ManipulatorPointId }, + SetClosed { index: usize, closed: bool }, + SetManipulatorHandleMirroring { id: ManipulatorGroupId, mirror_angle: bool }, + SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 }, + ToggleManipulatorHandleMirroring { id: ManipulatorGroupId }, +} diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs new file mode 100644 index 0000000000..80e0d2f12b --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -0,0 +1,262 @@ +use crate::messages::prelude::*; + +use document_legacy::document::Document; +use document_legacy::{LayerId, Operation}; +use glam::{DAffine2, DVec2}; +use graph_craft::document::value::TaggedValue; +use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork}; +use graphene_core::vector::style::{Fill, FillType, Stroke}; +use transform_utils::LayerBounds; + +use super::{resolve_document_node_type, VectorDataModification}; + +mod transform_utils; + +#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] +pub struct GraphOperationMessageHandler; + +struct ModifyInputsContext<'a> { + network: &'a mut NodeNetwork, + node_graph: &'a mut NodeGraphMessageHandler, + responses: &'a mut VecDeque, + layer: &'a [LayerId], +} +impl<'a> ModifyInputsContext<'a> { + /// Get the node network from the document + fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque) -> Option { + document.layer_mut(&layer).ok().and_then(|layer| layer.as_node_graph_mut().ok()).map(|network| Self { + network, + node_graph, + responses, + layer, + }) + } + + /// Updates the input of an existing node + fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec)) { + let document_node = self.network.nodes.get_mut(&node_id).unwrap(); + update_input(&mut document_node.inputs); + } + /// Insert a new node and modify the inputs + fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec)) { + let output_node_id = self.network.outputs[0].node_id; + let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else { + warn!("Output node doesn't exist"); + return; + }; + + let metadata = output_node.metadata.clone(); + let new_input = output_node.inputs[0].clone(); + let node_id = generate_uuid(); + output_node.inputs[0] = NodeInput::node(node_id, 0); + + let Some(node_type) = resolve_document_node_type(name) else { + warn!("Node type \"{name}\" doesn't exist"); + return; + }; + let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata); + update_input(&mut new_document_node.inputs); + self.network.nodes.insert(node_id, new_document_node); + } + + /// Changes the inputs of a specific node + fn modify_inputs(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec)) { + let node_id = self.network.primary_flow().find(|(node, _)| node.name == name).map(|(_, id)| id); + if let Some(node_id) = node_id { + self.modify_existing_node_inputs(node_id, update_input); + } else { + self.modify_new_node(name, update_input); + } + self.node_graph.layer_path = Some(self.layer.to_vec()); + self.node_graph.nested_path.clear(); + self.responses.add(PropertiesPanelMessage::ResendActiveProperties); + let layer_path = self.layer.to_vec(); + self.responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path }); + } + fn fill_set(&mut self, fill: Fill) { + self.modify_inputs("Fill", |inputs| { + let fill_type = match fill { + Fill::None => FillType::None, + Fill::Solid(_) => FillType::Solid, + Fill::Gradient(_) => FillType::Gradient, + }; + inputs[1] = NodeInput::value(TaggedValue::FillType(fill_type), false); + if Fill::None == fill { + inputs[2] = NodeInput::value(TaggedValue::OptionalColor(None), false); + } else if let Fill::Solid(color) = fill { + inputs[2] = NodeInput::value(TaggedValue::OptionalColor(Some(color)), false); + } else if let Fill::Gradient(gradient) = fill { + inputs[3] = NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false); + inputs[4] = NodeInput::value(TaggedValue::DVec2(gradient.start), false); + inputs[5] = NodeInput::value(TaggedValue::DVec2(gradient.end), false); + inputs[6] = NodeInput::value(TaggedValue::DAffine2(gradient.transform), false); + inputs[7] = NodeInput::value(TaggedValue::GradientPositions(gradient.positions), false); + } + }); + } + fn stroke_set(&mut self, stroke: Stroke) { + self.modify_inputs("Stroke", |inputs| { + inputs[1] = NodeInput::value(TaggedValue::Color(stroke.color.unwrap_or_default()), false); + inputs[2] = NodeInput::value(TaggedValue::F64(stroke.weight), false); + inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false); + inputs[4] = NodeInput::value(TaggedValue::F64(stroke.dash_offset), false); + inputs[5] = NodeInput::value(TaggedValue::LineCap(stroke.line_cap), false); + inputs[6] = NodeInput::value(TaggedValue::LineJoin(stroke.line_join), false); + inputs[7] = NodeInput::value(TaggedValue::F64(stroke.line_join_miter_limit), false); + }); + } + + fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) { + self.modify_inputs("Transform", |inputs| { + let layer_transform = transform_utils::get_current_transform(inputs); + let to = match transform_in { + TransformIn::Local => DAffine2::IDENTITY, + TransformIn::Scope { scope } => scope * parent_transform, + TransformIn::Viewport => parent_transform, + }; + let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs))); + let transform = to.inverse() * pivot.inverse() * transform * pivot * to * layer_transform; + transform_utils::update_transform(inputs, transform); + }); + } + fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) { + self.modify_inputs("Transform", |inputs| { + let to = match transform_in { + TransformIn::Local => DAffine2::IDENTITY, + TransformIn::Scope { scope } => scope * parent_transform, + TransformIn::Viewport => parent_transform, + }; + let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs))); + let transform = to.inverse() * pivot.inverse() * transform * pivot; + transform_utils::update_transform(inputs, transform); + }); + } + fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) { + self.modify_inputs("Transform", |inputs| { + let layer_transform = transform_utils::get_current_transform(inputs); + let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs))); + let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot)); + let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform; + transform_utils::update_transform(inputs, transform); + inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false); + }); + } + + fn vector_modify(&mut self, modification: VectorDataModification) { + let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE]; + let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE]; + + self.modify_inputs("Path Generator", |inputs| { + let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else { + panic!("Path generator does not have subpath and mirror angle inputs"); + }; + + let NodeInput::Value { + tagged_value: TaggedValue::Subpaths(subpaths), + .. + } = subpaths else{ + return; + }; + let NodeInput::Value { + tagged_value: TaggedValue::ManipulatorGroupIds(mirror_angle_groups), + .. + } = mirror_angle_groups else{ + return; + }; + + [old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths); + + transform_utils::VectorModificationState { subpaths, mirror_angle_groups }.modify(modification); + + [new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths); + }); + self.modify_inputs("Transform", |inputs| { + let layer_transform = transform_utils::get_current_transform(inputs); + let normalised_pivot = transform_utils::get_current_normalised_pivot(inputs); + + let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalised_pivot + old_bounds_min; + let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalised_pivot + new_bounds_min; + let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot); + let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot); + + let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform; + transform_utils::update_transform(inputs, transform); + }); + } +} + +impl MessageHandler for GraphOperationMessageHandler { + fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, (document, node_graph): (&mut Document, &mut NodeGraphMessageHandler)) { + match message { + GraphOperationMessage::FillSet { layer, fill } => { + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.fill_set(fill); + } else { + responses.add(Operation::SetLayerFill { path: layer, fill }); + } + } + + GraphOperationMessage::StrokeSet { layer, stroke } => { + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.stroke_set(stroke); + } else { + responses.add(Operation::SetLayerStroke { path: layer, stroke }); + } + } + + GraphOperationMessage::TransformChange { layer, transform, transform_in } => { + let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); + let bounds = LayerBounds::new(document, &layer); + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.transform_change(transform, transform_in, parent_transform, bounds); + } else { + let transform = transform.to_cols_array(); + responses.add(match transform_in { + TransformIn::Local => Operation::TransformLayer { path: layer, transform }, + TransformIn::Scope { scope } => { + let scope = scope.to_cols_array(); + Operation::TransformLayerInScope { path: layer, transform, scope } + } + TransformIn::Viewport => Operation::TransformLayerInViewport { path: layer, transform }, + }); + } + } + GraphOperationMessage::TransformSet { layer, transform, transform_in } => { + let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); + let bounds = LayerBounds::new(document, &layer); + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.transform_set(transform, transform_in, parent_transform, bounds); + } else { + let transform = transform.to_cols_array(); + responses.add(match transform_in { + TransformIn::Local => Operation::SetLayerTransform { path: layer, transform }, + TransformIn::Scope { scope } => { + let scope = scope.to_cols_array(); + Operation::SetLayerTransformInScope { path: layer, transform, scope } + } + TransformIn::Viewport => Operation::SetLayerTransformInViewport { path: layer, transform }, + }); + } + } + GraphOperationMessage::TransformSetPivot { layer, pivot } => { + let bounds = LayerBounds::new(document, &layer); + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.pivot_set(pivot, bounds); + } + + let pivot = pivot.into(); + responses.add(Operation::SetPivot { layer_path: layer, pivot }); + } + + GraphOperationMessage::Vector { layer, modification } => { + if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + modify_inputs.vector_modify(modification); + } + } + } + } + + fn actions(&self) -> ActionList { + actions!(GraphOperationMessage; ) + } +} diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs new file mode 100644 index 0000000000..f7fef427f2 --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs @@ -0,0 +1,301 @@ +use crate::messages::portfolio::document::node_graph::VectorDataModification; +use bezier_rs::{ManipulatorGroup, Subpath}; +use document_legacy::document::Document; +use glam::{DAffine2, DVec2}; +use graph_craft::document::{value::TaggedValue, NodeInput}; +use graphene_core::uuid::ManipulatorGroupId; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; + +/// Convert an affine transform into scale angle translation and shear, assuming shear.y = 0. +pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) { + let x_axis = transform.matrix2.x_axis; + let y_axis = transform.matrix2.y_axis; + + // Assuming there is no vertical shear + let angle = x_axis.y.atan2(x_axis.x); + let (sin, cos) = angle.sin_cos(); + let scale_x = if cos.abs() > 1e-10 { x_axis.x / cos } else { x_axis.y / sin }; + + let mut shear_x = (sin * y_axis.y + cos * y_axis.x) / (sin * sin * scale_x + cos * cos * scale_x); + if !shear_x.is_finite() { + shear_x = 0.; + } + let scale_y = if cos.abs() > 1e-10 { + (y_axis.y - scale_x * sin * shear_x) / cos + } else { + (scale_x * cos * shear_x - y_axis.x) / sin + }; + let translation = transform.translation; + let scale = DVec2::new(scale_x, scale_y); + let shear = DVec2::new(shear_x, 0.); + (scale, angle, translation, shear) +} + +/// Update the inputs of the transform node to match a new transform +pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) { + let (scale, angle, translation, skew) = compute_scale_angle_translation_shear(transform); + + inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false); + inputs[2] = NodeInput::value(TaggedValue::F64(angle), false); + inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false); + inputs[4] = NodeInput::value(TaggedValue::DVec2(skew), false); +} + +/// TODO: This should be extracted from the graph at the location of the transform node. +pub struct LayerBounds { + pub bounds: [DVec2; 2], + pub bounds_transform: DAffine2, + pub layer_transform: DAffine2, +} + +impl LayerBounds { + /// Extract the layer bounds and their transform for a layer. + pub fn new(document: &Document, layer_path: &[u64]) -> Self { + let layer = document.layer(layer_path).ok(); + let bounds = layer + .and_then(|layer| layer.as_graph_frame().ok()) + .and_then(|frame| frame.vector_data.as_ref().map(|vector| vector.nonzero_bounding_box())) + .unwrap_or([DVec2::ZERO, DVec2::ONE]); + let bounds_transform = DAffine2::IDENTITY; + let layer_transform = document.multiply_transforms(layer_path).unwrap_or_default(); + Self { + bounds, + bounds_transform, + layer_transform, + } + } + pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 { + self.bounds[0] + (self.bounds[1] - self.bounds[0]) * normalised_pivot + } + pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 { + self.bounds_transform.transform_point2(self.layerspace_pivot(normalised_pivot)) + } +} + +/// Get the current affine transform from the transform node's inputs +pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 { + let translation = if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(translation), + .. + } = inputs[1] + { + translation + } else { + DVec2::ZERO + }; + let angle = if let NodeInput::Value { + tagged_value: TaggedValue::F64(angle), + .. + } = inputs[2] + { + angle + } else { + 0. + }; + let scale = if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(scale), + .. + } = inputs[3] + { + scale + } else { + DVec2::ONE + }; + let shear = if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(shear), + .. + } = inputs[4] + { + shear + } else { + DVec2::ZERO + }; + DAffine2::from_scale_angle_translation(scale, angle, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) +} + +/// Extract the current normalised pivot from the layer +pub fn get_current_normalised_pivot(inputs: &[NodeInput]) -> DVec2 { + if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(pivot), + .. + } = inputs[5] + { + pivot + } else { + DVec2::splat(0.5) + } +} + +/// +// \begin{bmatrix} +// S_{x}\cos(\theta)-S_{y}\sin(\theta)H_{y} & S_{x}\cos(\theta)H_{x}-S_{y}\sin(\theta) & T_{x}\\ +// S_{x}\sin(\theta)+S_{y}\cos(\theta)H_{y} & S_{x}\sin(\theta)H_{x}+S_{y}\cos(\theta) & T_{y}\\ +// 0 & 0 & 1 +// \end{bmatrix} +#[test] +fn derive_transform() { + for shear_x in -10..=10 { + let shear_x = (shear_x as f64) / 2.; + for angle in (0..=360).step_by(15) { + let angle = (angle as f64).to_radians(); + for scale_x in 1..10 { + let scale_x = (scale_x as f64) / 5.; + for scale_y in 1..10 { + let scale_y = (scale_y as f64) / 5.; + + let shear = DVec2::new(shear_x, 0.); + let scale = DVec2::new(scale_x, scale_y); + let translate = DVec2::new(5666., 644.); + + let origional_transform = DAffine2::from_cols( + DVec2::new(scale.x * angle.cos() - scale.y * angle.sin() * shear.y, scale.x * angle.sin() + scale.y * angle.cos() * shear.y), + DVec2::new(scale.x * angle.cos() * shear.x - scale.y * angle.sin(), scale.x * angle.sin() * shear.x + scale.y * angle.cos()), + translate, + ); + + let (new_scale, new_angle, new_translation, new_shear) = compute_scale_angle_translation_shear(origional_transform); + let new_transform = DAffine2::from_scale_angle_translation(new_scale, new_angle, new_translation) * DAffine2::from_cols_array(&[1., new_shear.y, new_shear.x, 1., 0., 0.]); + + assert!( + new_transform.abs_diff_eq(origional_transform, 1e-10), + "origional_transform {} new_transform {} / scale {} new_scale {} / angle {} new_angle {} / shear {} / new_shear {}", + origional_transform, + new_transform, + scale, + new_scale, + angle, + new_angle, + shear, + new_shear, + ); + } + } + } + } +} + +/// Expand a bounds to avoid div zero errors +fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] { + let bounds_size = bounds_max - bounds_min; + if bounds_size.x < 1e-10 { + bounds_max.x = bounds_min.x + 1.; + } + if bounds_size.y < 1e-10 { + bounds_max.y = bounds_min.y + 1.; + } + [bounds_min, bounds_max] +} +/// Returns corners of all subpaths +fn subpath_bounds(subpaths: &[Subpath]) -> [DVec2; 2] { + subpaths + .iter() + .filter_map(|subpath| subpath.bounding_box()) + .reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])]) + .unwrap_or_default() +} + +/// Returns corners of all subpaths (but expanded to avoid div zero errors) +pub fn nonzero_subpath_bounds(subpaths: &[Subpath]) -> [DVec2; 2] { + let [bounds_min, bounds_max] = subpath_bounds(subpaths); + clamp_bounds(bounds_min, bounds_max) +} + +pub struct VectorModificationState<'a> { + pub subpaths: &'a mut Vec>, + pub mirror_angle_groups: &'a mut Vec, +} +impl<'a> VectorModificationState<'a> { + fn insert_start(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup) { + self.subpaths[subpath_index].insert_manipulator_group(0, manipulator_group) + } + + fn insert_end(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup) { + let subpath = &mut self.subpaths[subpath_index]; + subpath.insert_manipulator_group(subpath.len(), manipulator_group) + } + fn insert(&mut self, manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId) { + for subpath in self.subpaths.iter_mut() { + if let Some(index) = subpath.manipulator_index_from_id(after_id) { + subpath.insert_manipulator_group(index + 1, manipulator_group); + break; + } + } + } + fn remove_group(&mut self, id: ManipulatorGroupId) { + for subpath in self.subpaths.iter_mut() { + if let Some(index) = subpath.manipulator_index_from_id(id) { + subpath.remove_manipulator_group(index); + break; + } + } + } + fn remove_point(&mut self, point: ManipulatorPointId) { + for subpath in self.subpaths.iter_mut() { + if point.manipulator_type == SelectedType::Anchor { + if let Some(index) = subpath.manipulator_index_from_id(point.group) { + subpath.remove_manipulator_group(index); + break; + } + } else if let Some(group) = subpath.manipulator_mut_from_id(point.group) { + if point.manipulator_type == SelectedType::InHandle { + group.in_handle = None; + } else if point.manipulator_type == SelectedType::OutHandle { + group.out_handle = None; + } + } + } + } + fn set_mirror(&mut self, id: ManipulatorGroupId, mirror_angle: bool) { + if !mirror_angle { + self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id); + } else if !self.mirror_angle_groups.contains(&id) { + self.mirror_angle_groups.push(id); + } + } + fn toggle_mirror(&mut self, id: ManipulatorGroupId) { + if self.mirror_angle_groups.contains(&id) { + self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id); + } else { + self.mirror_angle_groups.push(id); + } + } + fn set_position(&mut self, point: ManipulatorPointId, position: DVec2) { + for subpath in self.subpaths.iter_mut() { + if let Some(manipulator) = subpath.manipulator_mut_from_id(point.group) { + match point.manipulator_type { + SelectedType::Anchor => manipulator.anchor = position, + SelectedType::InHandle => manipulator.in_handle = Some(position), + SelectedType::OutHandle => manipulator.out_handle = Some(position), + } + if point.manipulator_type != SelectedType::Anchor && self.mirror_angle_groups.contains(&point.group) { + let reflect = |opposite: DVec2| { + (manipulator.anchor - position) + .try_normalize() + .map(|direction| direction * (opposite - manipulator.anchor).length() + manipulator.anchor) + .unwrap_or(opposite) + }; + match point.manipulator_type { + SelectedType::InHandle => manipulator.out_handle = manipulator.out_handle.map(reflect), + SelectedType::OutHandle => manipulator.in_handle = manipulator.in_handle.map(reflect), + _ => {} + } + } + + break; + } + } + } + pub fn modify(&mut self, modification: VectorDataModification) { + match modification { + VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group } => self.insert_end(subpath_index, manipulator_group), + VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group } => self.insert_start(subpath_index, manipulator_group), + VectorDataModification::AddManipulatorGroup { manipulator_group, after_id } => self.insert(manipulator_group, after_id), + VectorDataModification::RemoveManipulatorGroup { id } => self.remove_group(id), + VectorDataModification::RemoveManipulatorPoint { point } => self.remove_point(point), + VectorDataModification::SetClosed { index, closed } => self.subpaths[index].set_closed(closed), + VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle } => self.set_mirror(id, mirror_angle), + VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position), + VectorDataModification::ToggleManipulatorHandleMirroring { id } => self.toggle_mirror(id), + } + } +} diff --git a/editor/src/messages/portfolio/document/node_graph/mod.rs b/editor/src/messages/portfolio/document/node_graph/mod.rs index ca898440a3..bee5f9c494 100644 --- a/editor/src/messages/portfolio/document/node_graph/mod.rs +++ b/editor/src/messages/portfolio/document/node_graph/mod.rs @@ -5,3 +5,11 @@ mod node_graph_message_handler; pub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant}; #[doc(inline)] pub use node_graph_message_handler::*; + +mod graph_operation_message; +mod graph_operation_message_handler; + +#[doc(inline)] +pub use graph_operation_message::*; +#[doc(inline)] +pub use graph_operation_message_handler::*; diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 226ed020d0..6f7893d58b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -46,7 +46,7 @@ impl FrontendGraphDataType { TaggedValue::Image(_) => Self::Raster, TaggedValue::ImageFrame(_) => Self::Raster, TaggedValue::Color(_) => Self::Color, - TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) | TaggedValue::VectorData(_) => Self::Subpath, + TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath, _ => Self::General, } } @@ -238,23 +238,7 @@ impl NodeGraphMessageHandler { // If empty, show all nodes in the network starting with the output if self.selected_nodes.is_empty() { - let mut stack = network.outputs.iter().map(|output| output.node_id).collect::>(); - let mut nodes = Vec::new(); - while let Some(node_id) = stack.pop() { - let Some(document_node) = network.nodes.get(&node_id) else { - continue; - }; - - stack.extend( - document_node - .inputs - .iter() - .take(1) // Only show the primary input - .filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }), - ); - nodes.push((document_node, node_id)); - } - for &(document_node, node_id) in nodes.iter().rev() { + for (document_node, node_id) in network.primary_flow().collect::>().into_iter().rev() { sections.push(node_properties::generate_node_properties(document_node, node_id, context)); } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index b0b5272f3d..dd9b590410 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -668,25 +668,25 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Path Generator", category: "Vector", - identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator"), - inputs: vec![DocumentInputType { - name: "Path Data", - data_type: FrontendGraphDataType::Subpath, - default: NodeInput::value(TaggedValue::Subpath(bezier_rs::Subpath::new(Vec::new(), false)), false), - }], + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"), + inputs: vec![ + DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false), + DocumentInputType::value("Mirror", TaggedValue::ManipulatorGroupIds(vec![]), false), + ], outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], properties: node_properties::no_properties, }, DocumentNodeType { name: "Transform", category: "Vector", - identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _>"), + identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _>"), inputs: vec![ DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false), DocumentInputType::value("Rotation", TaggedValue::F64(0.), false), DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false), DocumentInputType::value("Skew", TaggedValue::DVec2(DVec2::ZERO), false), + DocumentInputType::value("Pivot", TaggedValue::DVec2(DVec2::splat(0.5)), false), ], outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], properties: node_properties::transform_properties, @@ -697,8 +697,8 @@ fn static_nodes() -> Vec { identifier: NodeImplementation::proto("graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>"), inputs: vec![ DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), - DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::Solid), false), - DocumentInputType::value("Solid Color", TaggedValue::Color(Color::BLACK), false), + DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::None), false), + DocumentInputType::value("Solid Color", TaggedValue::OptionalColor(None), false), DocumentInputType::value("Gradient Type", TaggedValue::GradientType(vector::style::GradientType::Linear), false), DocumentInputType::value("Start", TaggedValue::DVec2(DVec2::new(0., 0.5)), false), DocumentInputType::value("End", TaggedValue::DVec2(DVec2::new(1., 0.5)), false), @@ -871,7 +871,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw } } -pub fn new_vector_network(subpath: bezier_rs::Subpath) -> NodeNetwork { +pub fn new_vector_network(subpaths: Vec>) -> NodeNetwork { let input = resolve_document_node_type("Input").expect("Input node does not exist"); let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist"); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist"); @@ -891,7 +891,7 @@ pub fn new_vector_network(subpath: bezier_rs::Subpath) outputs: vec![NodeOutput::new(5, 0)], nodes: [ input.to_document_node_default_inputs([], next_pos()), - path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpath(subpath), false))], next_pos()), + path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()), transform.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()), fill.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()), stroke.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 75fdcdd47f..88cbce86e0 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -398,18 +398,24 @@ fn gradient_positions(rows: &mut Vec, document_node: &DocumentNode, fn color_widget(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, color_props: ColorInput, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist); - if let NodeInput::Value { - tagged_value: TaggedValue::Color(x), - exposed: false, - } = document_node.inputs[index] - { - widgets.extend_from_slice(&[ - WidgetHolder::unrelated_separator(), - color_props - .value(Some(x as Color)) - .on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index)) - .widget_holder(), - ]) + if let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] { + if let &TaggedValue::Color(x) = tagged_value { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_separator(), + color_props + .value(Some(x as Color)) + .on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index)) + .widget_holder(), + ]) + } else if let &TaggedValue::OptionalColor(x) = tagged_value { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_separator(), + color_props + .value(x) + .on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value), node_id, index)) + .widget_holder(), + ]) + } } LayoutGroup::Row { widgets } } @@ -574,7 +580,23 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont let translation = { let index = 1; - let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, true); + let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, false); + + let pivot_index = 5; + if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(pivot), + exposed: false, + } = document_node.inputs[pivot_index] + { + widgets.push(WidgetHolder::unrelated_separator()); + widgets.push( + PivotAssist::new(pivot.into()) + .on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into()) + .widget_holder(), + ); + } else { + add_blank_assist(&mut widgets); + } if let NodeInput::Value { tagged_value: TaggedValue::DVec2(vec2), @@ -1044,7 +1066,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte widgets.extend_from_slice(&[ WidgetHolder::unrelated_separator(), - DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).widget_holder(), + DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).tooltip("When selecing a layer in a folder, shallow select will select the parent folder whereas deep select will select the layer. Double clicking in shallow select mode will select the layer.").widget_holder(), ]); } LayoutGroup::Row { widgets }.with_tooltip("Algorithm used to generate the image during each sampling step") @@ -1289,7 +1311,8 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: let mut widgets = Vec::new(); let gradient = fill_type == Some(graphene_core::vector::style::FillType::Gradient); let solid = fill_type == Some(graphene_core::vector::style::FillType::Solid); - if fill_type.is_none() || solid { + let empty = fill_type == Some(graphene_core::vector::style::FillType::None); + if fill_type.is_none() || solid || empty { let solid_color = color_widget(document_node, node_id, solid_color_index, "Color", ColorInput::default(), true); widgets.push(solid_color); } @@ -1300,7 +1323,7 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: gradient_positions(&mut widgets, document_node, "Gradient Positions", node_id, positions_index); } - if gradient || solid { + if gradient || solid || empty { let new_fill_type = if gradient { FillType::Solid } else { FillType::Gradient }; let switch_button = TextButton::new(if gradient { "Use Solid Color" } else { "Use Gradient" }) .tooltip(if gradient { diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index 3552716a3c..1e40772a30 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -129,12 +129,12 @@ impl<'a> MessageHandler { - let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + let (layer, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let position: Option = new_position.into(); - let pivot = position.unwrap().into(); + let pivot = position.unwrap(); - responses.push_back(DocumentMessage::StartTransaction.into()); - responses.push_back(Operation::SetPivot { layer_path, pivot }.into()); + responses.add(DocumentMessage::StartTransaction); + responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); } CheckSelectedWasUpdated { path } => { if self.matches_selected(&path) { diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index c95ab0b810..1ae46f4485 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -4,7 +4,6 @@ use crate::messages::prelude::*; use document_legacy::document::Document; use document_legacy::layers::style::RenderData; use document_legacy::LayerId; -use document_legacy::Operation as DocumentOperation; use glam::{DAffine2, DVec2}; use std::collections::{HashMap, VecDeque}; @@ -152,6 +151,7 @@ impl TransformOperation { }; selected.update_transforms(transformation); + self.hints(snapping, selected.responses); } } @@ -176,6 +176,32 @@ impl TransformOperation { self.apply_transform_operation(selected, snapping); } + + pub fn hints(&self, snapping: bool, responses: &mut VecDeque) { + use crate::messages::input_mapper::utility_types::input_keyboard::Key; + use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; + + let mut hints = Vec::new(); + + let value_str = match self { + TransformOperation::None => String::new(), + TransformOperation::Grabbing(translation) => format!("Translate X: {} Y: {}", translation.to_dvec().x, translation.to_dvec().y), + TransformOperation::Rotating(rotation) => format!("Rotate {}°", rotation.to_f64(snapping)), + TransformOperation::Scaling(scale) => format!("Scale X: {} Y: {}", scale.to_dvec(snapping).x, scale.to_dvec(snapping).y), + }; + hints.push(HintInfo::label(value_str)); + hints.push(HintInfo::keys([Key::Shift], "Precision Mode")); + if matches!(self, TransformOperation::Rotating(_) | TransformOperation::Scaling(_)) { + hints.push(HintInfo::keys([Key::Control], "Snap")); + } + if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) { + hints.push(HintInfo::keys([Key::KeyX], "X Axis")); + hints.push(HintInfo::keys([Key::KeyY], "Y Axis")); + } + + let hint_data = HintData(vec![HintGroup(hints)]); + responses.add(FrontendMessage::UpdateInputHints { hint_data }); + } } pub struct Selected<'a> { @@ -239,13 +265,11 @@ impl<'a> Selected<'a> { let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); let new = to.inverse() * transformation * to * original_layer_transforms; - self.responses.push_back( - DocumentOperation::SetLayerTransform { - path: layer_path.to_vec(), - transform: new.to_cols_array(), - } - .into(), - ); + self.responses.add(GraphOperationMessage::TransformSet { + layer: layer_path.to_vec(), + transform: new, + transform_in: TransformIn::Local, + }); } self.responses.push_back(BroadcastEvent::DocumentIsDirty.into()); @@ -253,16 +277,14 @@ impl<'a> Selected<'a> { } pub fn revert_operation(&mut self) { - for path in self.selected { - if let Some(transform) = self.original_transforms.get(*path) { + for layer in self.selected { + if let Some(&transform) = self.original_transforms.get(*layer) { // Push front to stop document switching before sending the transform - self.responses.push_front( - DocumentOperation::SetLayerTransform { - path: path.to_vec(), - transform: transform.to_cols_array(), - } - .into(), - ); + self.responses.add(GraphOperationMessage::TransformSet { + layer: layer.to_vec(), + transform, + transform_in: TransformIn::Local, + }); } } } diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 0c0f0c91df..beb3d3e78c 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -16,6 +16,7 @@ pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPre pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler}; pub use crate::messages::portfolio::document::artboard::{ArtboardMessage, ArtboardMessageDiscriminant, ArtboardMessageHandler}; pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageDiscriminant, NavigationMessageHandler}; +pub use crate::messages::portfolio::document::node_graph::{GraphOperationMessage, GraphOperationMessageDiscriminant, GraphOperationMessageHandler}; pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler}; pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageDiscriminant, OverlaysMessageHandler}; pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler}; @@ -50,7 +51,22 @@ pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextT // Helper pub use crate::messages::globals::global_variables::*; +pub use crate::messages::portfolio::document::node_graph::TransformIn; pub use graphite_proc_macros::*; pub use std::collections::{HashMap, HashSet, VecDeque}; + +pub trait Responses { + fn add(&mut self, message: impl Into); + fn try_add(&mut self, message: Option>) { + if let Some(message) = message { + self.add(message); + } + } +} +impl Responses for VecDeque { + fn add(&mut self, message: impl Into) { + self.push_back(message.into()); + } +} diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs new file mode 100644 index 0000000000..f1071adf17 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -0,0 +1,25 @@ +use crate::messages::portfolio::document::node_graph; +use crate::messages::prelude::*; + +use bezier_rs::Subpath; +use document_legacy::{LayerId, Operation}; +use glam::DAffine2; +use graphene_core::uuid::ManipulatorGroupId; +use std::collections::VecDeque; + +/// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. +pub fn new_vector_layer(subpaths: Vec>, layer_path: Vec, responses: &mut VecDeque) { + responses.push_back(DocumentMessage::DeselectAllLayers.into()); + let network = node_graph::new_vector_network(subpaths); + + responses.push_back( + Operation::AddNodeGraphFrame { + path: layer_path.clone(), + insert_index: -1, + transform: DAffine2::ZERO.to_cols_array(), + network, + } + .into(), + ); + responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path }); +} diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index 611f1baad3..e79ef0a917 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -1,3 +1,4 @@ +pub mod graph_modification_utils; pub mod overlay_renderer; pub mod path_outline; pub mod pivot; diff --git a/editor/src/messages/tool/common_functionality/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs index e057f05293..ecaeb04390 100644 --- a/editor/src/messages/tool/common_functionality/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -3,97 +3,106 @@ use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS; use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT}; use crate::messages::prelude::*; +use bezier_rs::ManipulatorGroup; use document_legacy::document::Document; use document_legacy::layers::style::{self, Fill, Stroke}; use document_legacy::{LayerId, Operation}; use graphene_core::raster::color::Color; -use graphene_std::vector::consts::ManipulatorType; -use graphene_std::vector::manipulator_group::ManipulatorGroup; -use graphene_std::vector::manipulator_point::ManipulatorPoint; -use graphene_std::vector::subpath::Subpath; +use graphene_core::uuid::ManipulatorGroupId; use glam::{DAffine2, DVec2}; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; + +use super::shape_editor::SelectedShapeState; /// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor. -type ManipulatorGroupOverlays = [Option>; 5]; -type ManipulatorId = u64; +#[derive(Clone, Debug, Default)] +struct ManipulatorGroupOverlays { + pub anchor: Option>, + pub in_handle: Option>, + pub in_line: Option>, + pub out_handle: Option>, + pub out_line: Option>, +} +impl ManipulatorGroupOverlays { + pub fn iter<'a>(&'a self) -> impl Iterator>> { + [&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter() + } +} + +type GraphiteManipulatorGroup = ManipulatorGroup; const POINT_STROKE_WEIGHT: f64 = 2.; #[derive(Clone, Debug, Default)] pub struct OverlayRenderer { shape_overlay_cache: HashMap>, - manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>, + manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorGroupId), ManipulatorGroupOverlays>, } impl OverlayRenderer { pub fn new() -> Self { - OverlayRenderer { - manipulator_group_overlay_cache: HashMap::new(), - shape_overlay_cache: HashMap::new(), - } + Self::default() } - pub fn render_subpath_overlays(&mut self, document: &Document, layer_path: Vec, responses: &mut VecDeque) { + pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec, responses: &mut VecDeque) { let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap(); if let Ok(layer) = document.layer(&layer_path) { let layer_id = layer_path.last().unwrap(); self.layer_overlay_visibility(document, layer_path.clone(), true, responses); - if let Some(shape) = layer.as_subpath() { + if let Some(vector_data) = layer.as_vector_data() { let outline_cache = self.shape_overlay_cache.get(layer_id); trace!("Overlay: Outline cache {:?}", &outline_cache); // Create an outline if we do not have a cached one if outline_cache.is_none() { - let outline_path = self.create_shape_outline_overlay(shape.clone(), responses); + let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses); self.shape_overlay_cache.insert(*layer_id, outline_path.clone()); Self::place_outline_overlays(outline_path.clone(), &transform, responses); trace!("Overlay: Creating new outline {:?}", &outline_path); } else if let Some(outline_path) = outline_cache { trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id); - Self::modify_outline_overlays(outline_path.clone(), shape.clone(), responses); + Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses); Self::place_outline_overlays(outline_path.clone(), &transform, responses); } // Create, place, and style the manipulator overlays - for (manipulator_group_id, manipulator_group) in shape.manipulator_groups().enumerate() { - let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, *manipulator_group_id)).or_default(); + for manipulator_group in vector_data.manipulator_groups() { + let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, manipulator_group.id)).or_default(); // Only view in and out handles if they are not on top of the anchor let [in_handle, out_handle] = { - let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() else { - continue; - }; + let anchor = manipulator_group.anchor; - let anchor_position = transform.transform_point2(anchor.position); - let filter_position = |handle: &&ManipulatorPoint| transform.transform_point2(handle.position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - let filter_manipulator_point = |manipulator_type| manipulator_group.points[manipulator_type as usize].as_ref().filter(filter_position); - [filter_manipulator_point(ManipulatorType::InHandle), filter_manipulator_point(ManipulatorType::OutHandle)] + let anchor_position = transform.transform_point2(anchor); + let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + let filter_handle = |manipulator: Option| manipulator.filter(not_under_anchor); + [filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)] }; // Create anchor - manipulator_group_cache[0] = manipulator_group_cache[0].take().or_else(|| Some(Self::create_anchor_overlay(responses))); + manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses))); // Create or delete in handle if in_handle.is_none() { - Self::remove_overlay(manipulator_group_cache[1].take(), responses); - Self::remove_overlay(manipulator_group_cache[3].take(), responses); + Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses); + Self::remove_overlay(manipulator_group_cache.in_line.take(), responses); } else { - manipulator_group_cache[1] = manipulator_group_cache[1].take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses)); - manipulator_group_cache[3] = manipulator_group_cache[3].take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses)); + manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses)); + manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses)); } // Create or delete out handle if out_handle.is_none() { - Self::remove_overlay(manipulator_group_cache[2].take(), responses); - Self::remove_overlay(manipulator_group_cache[4].take(), responses); + Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses); + Self::remove_overlay(manipulator_group_cache.out_line.take(), responses); } else { - manipulator_group_cache[2] = manipulator_group_cache[2].take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses)); - manipulator_group_cache[4] = manipulator_group_cache[4].take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses)); + manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses)); + manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses)); } // Update placement and style Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses); - Self::style_overlays(manipulator_group, manipulator_group_cache, responses); + Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses); } // TODO Handle removing shapes from cache so we don't memory leak // Eventually will get replaced with am immediate mode renderer for overlays @@ -112,11 +121,12 @@ impl OverlayRenderer { // Remove the ManipulatorGroup overlays if let Ok(layer) = document.layer(&layer_path) { - if let Some(shape) = layer.as_subpath() { - for (id, _) in shape.manipulator_groups().enumerate() { - if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) { + if let Some(vector_data) = layer.as_vector_data() { + for manipulator_group in vector_data.manipulator_groups() { + let id = manipulator_group.id; + if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) { Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); - self.manipulator_group_overlay_cache.remove(&(*layer_id, *id)); + self.manipulator_group_overlay_cache.remove(&(*layer_id, id)); } } } @@ -133,9 +143,10 @@ impl OverlayRenderer { // Hide the manipulator group overlays if let Ok(layer) = document.layer(&layer_path) { - if let Some(shape) = layer.as_subpath() { - for (id, _) in shape.manipulator_groups().enumerate() { - if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) { + if let Some(vector_data) = layer.as_vector_data() { + for manipulator_group in vector_data.manipulator_groups() { + let id = manipulator_group.id; + if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) { Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses); } } @@ -144,7 +155,7 @@ impl OverlayRenderer { } /// Create the kurbo shape that matches the selected viewport shape. - fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque) -> Vec { + fn create_shape_outline_overlay(&self, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; let operation = Operation::AddShape { path: layer_path.clone(), @@ -185,7 +196,7 @@ impl OverlayRenderer { } /// Create a single handle overlay and return its layer id if it exists. - fn create_handle_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque) -> Option> { + fn create_handle_overlay_if_exists(handle: Option, responses: &mut VecDeque) -> Option> { handle.map(|_| Self::create_handle_overlay(responses)) } @@ -210,7 +221,7 @@ impl OverlayRenderer { } /// Create the shape outline overlay and return its layer ID. - fn create_handle_line_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque) -> Option> { + fn create_handle_line_overlay_if_exists(handle: Option, responses: &mut VecDeque) -> Option> { handle.as_ref().map(|_| Self::create_handle_line_overlay(responses)) } @@ -219,53 +230,48 @@ impl OverlayRenderer { responses.push_back(transform_message); } - fn modify_outline_overlays(outline_path: Vec, subpath: Subpath, responses: &mut VecDeque) { + fn modify_outline_overlays(outline_path: Vec, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque) { let outline_modify_message = Self::overlay_modify_message(outline_path, subpath); responses.push_back(outline_modify_message); } /// Updates the position of the overlays based on the [Subpath] points. - fn place_manipulator_group_overlays(manipulator_group: &ManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque) { - if let Some(manipulator_point) = &manipulator_group.points[ManipulatorType::Anchor] { - // Helper function to keep things DRY (don't-repeat-yourself) - let mut place_handle_and_line = |handle: &ManipulatorPoint, line_overlay: &mut Vec, marker_source: &mut Option>| { - let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position); - let scale = DVec2::splat(line_vector.length()); - let angle = -line_vector.angle_between(DVec2::X); - let translation = (parent_transform.transform_point2(handle.position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5); - let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); - responses.push_back(Self::overlay_transform_message(line_overlay.clone(), transform)); + fn place_manipulator_group_overlays(manipulator_group: &GraphiteManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque) { + let anchor = manipulator_group.anchor; + let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option>| { + let line_vector = parent_transform.transform_point2(anchor) - parent_transform.transform_point2(handle_position); + let scale = DVec2::splat(line_vector.length()); + let angle = -line_vector.angle_between(DVec2::X); + let translation = (parent_transform.transform_point2(handle_position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5); + let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); + responses.push_back(Self::overlay_transform_message(line_overlay.to_vec(), transform)); - let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses)); - let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); - let angle = 0.; - let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); - let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); - responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform)); - *marker_source = Some(marker_overlay); - }; + let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses)); + let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); + let angle = 0.; + let translation = (parent_transform.transform_point2(handle_position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); + let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); + responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform)); + *marker_source = Some(marker_overlay); + }; - // Place the handle overlays - let [_, h1, h2] = &manipulator_group.points; - let [a, b, c, line1, line2] = overlays; - let markers = [a, b, c]; - if let (Some(handle), Some(line_source)) = (h1.as_ref(), line1.as_mut()) { - place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]); - } - if let (Some(handle), Some(line_source)) = (h2.as_ref(), line2.as_mut()) { - place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]); - } + // Place the handle overlays + if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.in_handle, overlays.in_line.as_mut()) { + place_handle_and_line(handle_position, line_overlay, &mut overlays.in_handle); + } + if let (Some(handle_psoition), Some(line_overlay)) = (manipulator_group.out_handle, overlays.out_line.as_ref()) { + place_handle_and_line(handle_psoition, line_overlay, &mut overlays.out_handle); + } - // Place the anchor point overlay - if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] { - let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); - let angle = 0.; - let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); - let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); + // Place the anchor point overlay + if let Some(anchor_overlay) = &overlays.anchor { + let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); + let angle = 0.; + let translation = (parent_transform.transform_point2(anchor) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); + let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); - let message = Self::overlay_transform_message(anchor_overlay.clone(), transform); - responses.push_back(message); - } + let message = Self::overlay_transform_message(anchor_overlay.clone(), transform); + responses.push_back(message); } } @@ -310,24 +316,27 @@ impl OverlayRenderer { } /// Create an update message for an overlay. - fn overlay_modify_message(layer_path: Vec, subpath: Subpath) -> Message { + fn overlay_modify_message(layer_path: Vec, subpath: graphene_core::vector::Subpath) -> Message { DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into() } /// Sets the overlay style for this point. - fn style_overlays(manipulator_group: &ManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque) { + fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque) { // TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); - + let selected_shape_state = state.get(layer_path); // Update if the manipulator points are shown as selected // Here the index is important, even though overlays[..] has five elements we only care about the first three - for (index, point) in manipulator_group.points.iter().enumerate() { - if let Some(point) = point { - if let Some(overlay) = &overlays[index] { - let style = if point.editor_state.is_selected { selected_style.clone() } else { deselected_style.clone() }; - responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay.clone(), style }.into()).into()); - } + for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() { + let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index]; + if let Some(overlay_path) = overlay { + let selected = selected_shape_state + .filter(|state| state.is_selected(ManipulatorPointId::new(manipulator_group.id, selected_type))) + .is_some(); + + let style = if selected { selected_style.clone() } else { deselected_style.clone() }; + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay_path.clone(), style }.into()).into()); } } } diff --git a/editor/src/messages/tool/common_functionality/path_outline.rs b/editor/src/messages/tool/common_functionality/path_outline.rs index 077fdeb380..2e49e0c172 100644 --- a/editor/src/messages/tool/common_functionality/path_outline.rs +++ b/editor/src/messages/tool/common_functionality/path_outline.rs @@ -4,6 +4,7 @@ use crate::messages::prelude::*; use document_legacy::intersection::Quad; use document_legacy::layers::layer_info::LayerDataType; +use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer; use document_legacy::layers::style::{self, Fill, RenderData, Stroke}; use document_legacy::{LayerId, Operation}; use graphene_std::vector::subpath::Subpath; @@ -35,6 +36,7 @@ impl PathOutline { let subpath = match &document_layer.data { LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()), LayerDataType::Text(text) => Some(text.to_subpath_nonmut(render_data)), + LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(Subpath::from_bezier_crate(&vector_data.subpaths)), _ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)), }?; diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 7aed0c459c..6c8d036b62 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -171,8 +171,8 @@ impl Pivot { let pivot = transform.inverse().transform_point2(position); // Only update the pivot when computed position is finite. Infinite can happen when scale is 0. if pivot.is_finite() { - let layer_path = layer_path.to_owned(); - responses.push_back(Operation::SetPivot { layer_path, pivot: pivot.into() }.into()); + let layer = layer_path.to_owned(); + responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); } } } diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index 697876db9f..4290e3add5 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -5,7 +5,6 @@ use crate::messages::tool::common_functionality::snapping::SnapManager; use document_legacy::layers::style::RenderData; use document_legacy::LayerId; -use document_legacy::Operation; use glam::{DAffine2, DVec2, Vec2Swizzles}; @@ -54,9 +53,10 @@ impl Resize { } Some( - Operation::SetLayerTransformInViewport { - path: path.to_vec(), - transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(), + GraphOperationMessage::TransformSet { + layer: path.to_vec(), + transform: DAffine2::from_scale_angle_translation(size, 0., start), + transform_in: TransformIn::Viewport, } .into(), ) diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 0fdbc07e98..70511ec7f4 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1,32 +1,34 @@ +use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; -use bezier_rs::TValue; -use document_legacy::{LayerId, Operation}; -use graphene_std::vector::consts::ManipulatorType; -use graphene_std::vector::manipulator_group::ManipulatorGroup; -use graphene_std::vector::manipulator_point::ManipulatorPoint; -use graphene_std::vector::subpath::{BezierId, Subpath}; +use bezier_rs::{Bezier, TValue}; +use document_legacy::LayerId; +use graphene_core::uuid::ManipulatorGroupId; +use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData}; use document_legacy::document::Document; use glam::DVec2; -/// ShapeEditor is the container for all of the layer paths that are represented as [Subpath]s and provides -/// functionality required to query and create the [Subpath] / [ManipulatorGroup]s / [ManipulatorPoint]s. -/// -/// Overview: -/// ```text -/// ShapeEditor -/// | -/// selected_layers <- Paths to selected layers that may contain Subpaths -/// / | \ -/// Subpath ... Subpath <- Reference from layer paths, one Subpath per layer (for now, will eventually be a CompoundPath) -/// / | \ -/// ManipulatorGroup ... ManipulatorGroup <- Subpath contains many ManipulatorGroups -/// ``` #[derive(Clone, Debug, Default)] -pub struct ShapeEditor { +pub struct SelectedLayerState { + selected_points: HashSet, +} +impl SelectedLayerState { + pub fn is_selected(&self, point: ManipulatorPointId) -> bool { + self.selected_points.contains(&point) + } + pub fn select_point(&mut self, point: ManipulatorPointId) { + self.selected_points.insert(point); + } + pub fn deselect_point(&mut self, point: ManipulatorPointId) { + self.selected_points.remove(&point); + } +} +pub type SelectedShapeState = HashMap, SelectedLayerState>; +#[derive(Debug, Default)] +pub struct ShapeState { // The layers we can select and edit manipulators (anchors and handles) from - selected_layers: Vec>, + pub selected_shape_state: SelectedShapeState, } pub struct SelectedPointsInfo<'a> { @@ -36,333 +38,299 @@ pub struct SelectedPointsInfo<'a> { #[derive(Clone, Copy, Eq, PartialEq)] pub struct ManipulatorPointInfo<'a> { pub shape_layer_path: &'a [LayerId], - pub manipulator_group_id: u64, - pub manipulator_type: ManipulatorType, + pub point_id: ManipulatorPointId, } // TODO Consider keeping a list of selected manipulators to minimize traversals of the layers -impl ShapeEditor { +impl ShapeState { /// Select the first point within the selection threshold. /// Returns a tuple of the points if found and the offset, or `None` otherwise. - pub fn select_point(&self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool, responses: &mut VecDeque) -> Option { - if self.selected_layers.is_empty() { + pub fn select_point(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option { + if self.selected_shape_state.is_empty() { return None; } - if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) { - trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index); + if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) { + trace!("Selecting... manipulator point: {:?}", manipulator_point_id); - // If the point we're selecting has already been selected - // we can assume this point exists.. since we did just click on it hence the unwrap - let is_point_selected = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index] - .as_ref() - .unwrap() - .editor_state - .is_selected; + let vector_data = document.layer(&shape_layer_path).ok()?.as_vector_data()?; + let manipulator_group = vector_data.manipulator_groups().find(|group| group.id == manipulator_point_id.group)?; + let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?; - let point_position = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index] - .as_ref() - .unwrap() - .position; - - // The currently selected points (which are then modified to reflect the selection) - let mut points = self - .selected_layers() - .iter() - .filter_map(|path| document.layer(path).ok().map(|layer| (path, layer))) - .filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath))) - .flat_map(|(path, shape)| { - shape - .manipulator_groups() - .enumerate() - .filter(|(_id, manipulator_group)| manipulator_group.is_anchor_selected()) - .flat_map(|(id, manipulator_group)| manipulator_group.selected_points().map(move |point| (id, point.manipulator_type))) - .map(|(anchor, manipulator_point)| ManipulatorPointInfo { - shape_layer_path: path.as_slice(), - manipulator_group_id: *anchor, - manipulator_type: manipulator_point, - }) - }) - .collect::>(); + let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?; + let already_selected = selected_shape_state.is_selected(manipulator_point_id); // Should we select or deselect the point? - let should_select = if is_point_selected { !add_to_selection } else { true }; + let new_selected = if already_selected { !add_to_selection } else { true }; // This is selecting the manipulator only for now, next to generalize to points - if should_select { - // If we're replacing the selection, clear all points in other selected shapes - let add = add_to_selection || is_point_selected; - if !add { - points.clear(); - responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into()); + if new_selected { + let retain_existing_selection = add_to_selection || already_selected; + if !retain_existing_selection { + self.deselect_all(); } // Add to the selected points - let point_info = ManipulatorPointInfo { - shape_layer_path, - manipulator_group_id, - manipulator_type: ManipulatorType::from_index(manipulator_point_index), - }; - - points.push(point_info); - responses.push_back( - Operation::SelectManipulatorPoints { - layer_path: shape_layer_path.to_vec(), - point_ids: vec![(point_info.manipulator_group_id, point_info.manipulator_type)], - add, - } - .into(), - ); + let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; + selected_shape_state.select_point(manipulator_point_id); // Offset to snap the selected point to the cursor let offset = document - .generate_transform_relative_to_viewport(shape_layer_path) + .generate_transform_relative_to_viewport(&shape_layer_path) .map(|viewspace| mouse_position - viewspace.transform_point2(point_position)) .unwrap_or_default(); + let points = self + .selected_shape_state + .iter() + .flat_map(|(shape_layer_path, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { shape_layer_path, point_id })) + .collect(); + return Some(SelectedPointsInfo { points, offset }); } else { - responses.push_back( - Operation::DeselectManipulatorPoints { - layer_path: shape_layer_path.to_vec(), - point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))], - } - .into(), - ); - points.retain(|x| { - *x != ManipulatorPointInfo { - shape_layer_path, - manipulator_group_id, - manipulator_type: ManipulatorType::from_index(manipulator_point_index), - } - }); + let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; + selected_shape_state.deselect_point(manipulator_point_id); return None; } } // Deselect all points if no nearby point - responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into()); + self.deselect_all(); + None } - /// A wrapper for `find_nearest_point_indices()` and returns a [ManipulatorPoint]. - pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a ManipulatorPoint> { - let (shape_layer_path, manipulator_group_id, manipulator_point_index) = self.find_nearest_point_indices(document, mouse_position, select_threshold)?; - let selected_shape = self.shape(document, shape_layer_path).unwrap(); - if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) { - return manipulator_group.points[manipulator_point_index].as_ref(); - } - None + pub fn deselect_all(&mut self) { + self.selected_shape_state.values_mut().for_each(|state| state.selected_points.clear()); } /// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes. pub fn set_selected_layers(&mut self, target_layers: Vec>) { - self.selected_layers = target_layers; + self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path)); + for layer in target_layers { + self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default); + } } - pub fn selected_layers(&self) -> &Vec> { - &self.selected_layers - } - - pub fn selected_layers_ref(&self) -> Vec<&[LayerId]> { - self.selected_layers.iter().map(|l| l.as_slice()).collect::>() + pub fn selected_layers(&self) -> impl Iterator> { + self.selected_shape_state.keys() } /// Clear all of the shapes we can modify. pub fn clear_selected_layers(&mut self) { - self.selected_layers.clear(); + self.selected_shape_state.clear(); } pub fn has_selected_layers(&self) -> bool { - !self.selected_layers.is_empty() - } - - /// Provide the currently selected manipulators by reference. - pub fn selected_manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator { - self.iter(document).flat_map(|shape| shape.selected_manipulator_groups()) + !self.selected_shape_state.is_empty() } /// A mutable iterator of all the manipulators, regardless of selection. - pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator { - self.iter(document).flat_map(|shape| shape.manipulator_groups().iter()) + pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator> { + self.iter(document).flat_map(|shape| shape.manipulator_groups()) } // Sets the selected points to all points for the corresponding intersection - pub fn select_all_anchors(&self, responses: &mut VecDeque, itersections: Vec) { - responses.push_back(Operation::SelectAllAnchors { layer_path: itersections }.into()); + pub fn select_all_anchors(&mut self, document: &Document, layer_path: &[LayerId]) { + let Ok(layer) = document.layer(layer_path) else { return }; + let Some(vector_data) = layer.as_vector_data() else { return }; + let Some(state) = self.selected_shape_state.get_mut(layer_path) else { return }; + for manipulator in vector_data.manipulator_groups() { + state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor)) + } } /// Provide the currently selected points by reference. - pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator { - self.selected_manipulator_groups(document).flat_map(|manipulator_group| manipulator_group.selected_points()) + pub fn selected_points<'a>(&'a self) -> impl Iterator { + self.selected_shape_state.values().flat_map(|state| &state.selected_points) } /// Move the selected points by dragging the mouse. - pub fn move_selected_points(&self, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque) { - for layer_path in &self.selected_layers { - responses.push_back( - DocumentMessage::MoveSelectedManipulatorPoints { - layer_path: layer_path.clone(), - delta: (delta.x, delta.y), - mirror_distance, + pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque) { + for (layer_path, state) in &self.selected_shape_state { + let Ok(layer) = document.layer(&layer_path) else { continue }; + let Some(vector_data) = layer.as_vector_data() else { continue }; + + let transform = document.multiply_transforms(&layer_path).unwrap_or_default(); + let delta = transform.inverse().transform_vector2(delta); + + for &point in state.selected_points.iter() { + if point.manipulator_type.is_handle() && state.is_selected(ManipulatorPointId::new(point.group, SelectedType::Anchor)) { + continue; } - .into(), - ); + + let Some(group) = vector_data.manipulator_from_id(point.group) else { continue }; + + let mut move_point = |point: ManipulatorPointId| { + let Some(previous_position) = point.manipulator_type.get_position(group) else { return }; + let position = previous_position + delta; + responses.add(GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); + }; + + move_point(point); + + if point.manipulator_type == SelectedType::Anchor { + move_point(ManipulatorPointId::new(point.group, SelectedType::InHandle)); + move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle)); + } + + if mirror_distance && point.manipulator_type != SelectedType::Anchor && vector_data.mirror_angle.contains(&point.group) { + let Some(mut origional_handle_position) = point.manipulator_type.get_position(group) else { continue }; + origional_handle_position += delta; + + let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite()); + if state.is_selected(point) { + continue; + } + let position = group.anchor - (origional_handle_position - group.anchor); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); + } + } } } /// The opposing handle lengths. - pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap, HashMap> { - self.selected_layers() + pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap, HashMap> { + self.selected_shape_state .iter() - .filter_map(|path| document.layer(path).ok().map(|layer| (path, layer))) - .filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath))) - .map(|(path, shape)| { - let opposing_handle_lengths = shape - .manipulator_groups() - .enumerate() - .filter_map(|(id, manipulator_group)| { - // We will keep track of the opposing handle length when: - // i) Both handles exist and exactly one is selected. - // ii) The anchor is not selected. - // iii) We have to mirror the angle between handles. + .filter_map(|(path, state)| { + let layer = document.layer(path).ok()?; + let vector_data = layer.as_vector_data()?; + let opposing_handle_lengths = vector_data + .subpaths + .iter() + .flat_map(|subpath| { + subpath.manipulator_groups().iter().filter_map(|manipulator_group| { + // We will keep track of the opposing handle length when: + // i) Both handles exist and exactly one is selected. + // ii) The anchor is not selected. + // iii) We have to mirror the angle between handles. - if !manipulator_group.editor_state.mirror_angle_between_handles { - return None; - } + let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)); + let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)); + let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor)); - let mut selected_handles = manipulator_group.selected_handles(); - let handle = selected_handles.next()?; - - // Check that handle is the only selected handle. - if selected_handles.next().is_none() { - let opposing_handle_position = manipulator_group.opposing_handle(handle)?.position; - let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - if !anchor.is_selected() { - let opposing_handle_length = opposing_handle_position.distance(anchor.position); - Some((*id, opposing_handle_length)) - } else { - None + if anchor_selected { + return None; } - } else { - None - } + + let single_selected_handle = match (in_handle_selected, out_handle_selected) { + (true, false) => SelectedType::InHandle, + (false, true) => SelectedType::OutHandle, + _ => return None, + }; + + let opposing_handle_position = single_selected_handle.opposite().get_position(manipulator_group)?; + + let opposing_handle_length = opposing_handle_position.distance(manipulator_group.anchor); + Some((manipulator_group.id, opposing_handle_length)) + }) }) .collect::>(); - (path.clone(), opposing_handle_lengths) + Some((path.clone(), opposing_handle_lengths)) }) .collect::>() } /// Reset the opposing handle lengths. - pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap, HashMap>, responses: &mut VecDeque) { - self.selected_layers() - .iter() - .filter_map(|path| document.layer(path).ok().map(|layer| (path, layer))) - .filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath))) - .filter_map(|(path, shape)| opposing_handle_lengths.get(path).map(|layer_opposing_handle_lengths| (path, shape, layer_opposing_handle_lengths))) - .flat_map(|(path, shape, layer_opposing_handle_lengths)| { - shape - .manipulator_groups() - .enumerate() - .map(move |(id, manipulator_group)| (path, layer_opposing_handle_lengths, id, manipulator_group)) - }) - .for_each(|(path, layer_opposing_handle_lengths, id, manipulator_group)| { - if !manipulator_group.editor_state.mirror_angle_between_handles { - return; - } + pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap, HashMap>, responses: &mut VecDeque) { + for (path, state) in &self.selected_shape_state { + let Ok(layer) = document.layer(path) else { continue }; + let Some(vector_data) = layer.as_vector_data() else { continue }; + let Some(opposing_handle_lengths) = opposing_handle_lengths.get(path) else { continue }; - let opposing_handle_length = if let Some(length) = layer_opposing_handle_lengths.get(id) { - length - } else { - return; - }; - - let mut selected_handles = manipulator_group.selected_handles(); - let handle = if let Some(handle) = selected_handles.next() { - handle - } else { - return; - }; - - // Check that handle is the only selected handle. - if selected_handles.next().is_none() { - let opposing_handle = if let Some(opposing_handle) = manipulator_group.opposing_handle(handle) { - opposing_handle - } else { - return; - }; - - let anchor = if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() { - anchor - } else { - return; - }; - if anchor.is_selected() { - return; + for subpath in &vector_data.subpaths { + for manipulator_group in subpath.manipulator_groups() { + if !vector_data.mirror_angle.contains(&manipulator_group.id) { + continue; } - if let Some(offset) = (opposing_handle.position - anchor.position).try_normalize() { - let new_opposing_handle_position = anchor.position + offset * (*opposing_handle_length); - assert!(new_opposing_handle_position.is_finite(), "Opposing handle not finite!"); - responses.push_back( - Operation::MoveManipulatorPoint { - layer_path: path.clone(), - id: *id, - manipulator_type: opposing_handle.manipulator_type, - position: new_opposing_handle_position.into(), - } - .into(), - ); + let Some(opposing_handle_length) = opposing_handle_lengths.get(&manipulator_group.id) else { continue }; + + let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)); + let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)); + let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor)); + + if anchor_selected { + continue; } + + let single_selected_handle = match (in_handle_selected, out_handle_selected) { + (true, false) => SelectedType::InHandle, + (false, true) => SelectedType::OutHandle, + _ => continue, + }; + + let Some(opposing_handle) = single_selected_handle.opposite().get_position(manipulator_group) else { continue }; + + let Some(offset) = (opposing_handle - manipulator_group.anchor).try_normalize() else { continue }; + + let point = ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite()); + let position = manipulator_group.anchor + offset * (*opposing_handle_length); + assert!(position.is_finite(), "Opposing handle not finite!"); + + responses.add(GraphOperationMessage::Vector { + layer: path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); } - }); + } + } } /// Dissolve the selected points. pub fn delete_selected_points(&self, responses: &mut VecDeque) { - responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into()); - } - - /// Toggle if the handles should mirror angle across the anchor position. - pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, responses: &mut VecDeque) { - for layer_path in &self.selected_layers { - responses.push_back( - DocumentMessage::ToggleSelectedHandleMirroring { - layer_path: layer_path.clone(), - toggle_angle, - } - .into(), - ); + for (layer, state) in &self.selected_shape_state { + for &point in &state.selected_points { + responses.add(GraphOperationMessage::Vector { + layer: layer.to_vec(), + modification: VectorDataModification::RemoveManipulatorPoint { point }, + }) + } } } - /// Deselect all manipulators from the shapes that the manipulation handler has created. - pub fn deselect_all_points(&self, responses: &mut VecDeque) { - responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into()); + /// Toggle if the handles should mirror angle across the anchor position. + pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque) { + for (layer, state) in &self.selected_shape_state { + for point in &state.selected_points { + responses.add(GraphOperationMessage::Vector { + layer: layer.to_vec(), + modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group }, + }) + } + } } /// Iterate over the shapes. - pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator + 'a { - self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_subpath()) + pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator + 'a { + self.selected_shape_state + .keys() + .flat_map(|layer_id| document.layer(layer_id)) + .filter_map(|shape| shape.as_vector_data()) } /// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint]. - /// Return value is an `Option` of the tuple representing `(layer path, ManipulatorGroup ID, ManipulatorType enum index)`. - fn find_nearest_point_indices(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> { - if self.selected_layers.is_empty() { + pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec, ManipulatorPointId)> { + if self.selected_shape_state.is_empty() { return None; } let select_threshold_squared = select_threshold * select_threshold; // Find the closest control point among all elements of shapes_to_modify - for layer in self.selected_layers.iter() { - if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) { + for layer in self.selected_shape_state.keys() { + if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) { // Choose the first point under the threshold if distance_squared < select_threshold_squared { - trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index); - return Some((layer, manipulator_id, manipulator_point_index)); + trace!("Selecting... manipulator point: {:?}", manipulator_point_id); + return Some((layer.clone(), manipulator_point_id)); } } } @@ -373,51 +341,54 @@ impl ShapeEditor { // TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n)) /// Find the closest manipulator, manipulator point, and distance so we can select path elements. /// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time. - /// Return value is an `Option` of the tuple representing `(manipulator ID, manipulator point index, distance squared)`. - fn closest_point_in_layer(&self, document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(u64, usize, f64)> { - let mut closest_distance_squared: f64 = f64::MAX; // Not ideal - let mut result: Option<(u64, usize, f64)> = None; + /// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`. + fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> { + let mut closest_distance_squared: f64 = f64::MAX; + let mut result = None; - if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() { - let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; - for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() { - let manipulator_point_index = manipulator.closest_point(&viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE); - if let Some(point) = &manipulator.points[manipulator_point_index] { - if point.editor_state.can_be_selected { - let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos); - if distance_squared < closest_distance_squared { - closest_distance_squared = distance_squared; - result = Some((*manipulator_id, manipulator_point_index, distance_squared)); - } - } + let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; + let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; + for subpath in &vector_data.subpaths { + for manipulator in subpath.manipulator_groups() { + let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE); + + if distance_squared < closest_distance_squared { + closest_distance_squared = distance_squared; + result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared)); } } } + result } /// Find the `t` value along the path segment we have clicked upon, together with that segment ID. /// - /// Returns a tuple of [`BezierId`] and `t` as an f64. - fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(BezierId, f64)> { + /// Returns a tuple of subpath_index, manipulator_start and `t` as an f64. + fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> { let transform = document.generate_transform_relative_to_viewport(layer_path).ok()?; let layer_pos = transform.inverse().transform_point2(position); let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() }; - let mut result: Option<(BezierId, f64)> = None; + let mut result = None; let mut closest_distance_squared: f64 = tolerance * tolerance; - for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() { - let bezier = bezier_id.internal; - let t = bezier.project(layer_pos, projection_options); - let layerspace = bezier.evaluate(TValue::Parametric(t)); + let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; - let screenspace = transform.transform_point2(layerspace); - let distance_squared = screenspace.distance_squared(position); + for subpath in &vector_data.subpaths { + for (manipulator_index, bezier) in subpath.iter().enumerate() { + let t = bezier.project(layer_pos, projection_options); + let layerspace = bezier.evaluate(TValue::Parametric(t)); - if distance_squared < closest_distance_squared { - closest_distance_squared = distance_squared; - result = Some((bezier_id, t)); + let screenspace = transform.transform_point2(layerspace); + let distance_squared = screenspace.distance_squared(position); + + if distance_squared < closest_distance_squared { + closest_distance_squared = distance_squared; + let start = subpath.manipulator_groups()[manipulator_index]; + let end = subpath.manipulator_groups()[(manipulator_index + 1) % subpath.len()]; + result = Some((start.id, end.id, bezier, t)); + } } } @@ -426,34 +397,36 @@ impl ShapeEditor { /// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool). pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque) { - for layer_path in &self.selected_layers { - if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) { - let [first, second] = bezier_id.internal.split(TValue::Parametric(t)); + for layer_path in self.selected_layers() { + if let Some((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) { + let [first, second] = bezier.split(TValue::Parametric(t)); // Adjust the first manipulator group's out handle - let out_handle = Operation::SetManipulatorPoints { - layer_path: layer_path.clone(), - id: bezier_id.start, - manipulator_type: ManipulatorType::OutHandle, - position: first.handle_start().map(|p| p.into()), + let point = ManipulatorPointId::new(start, SelectedType::OutHandle); + let position = first.handle_start().unwrap_or(first.start()); + let out_handle = GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, }; + responses.add(out_handle); // Insert a new manipulator group between the existing ones - let insert = Operation::InsertManipulatorGroup { - layer_path: layer_path.clone(), - manipulator_group: ManipulatorGroup::new_with_handles(first.end(), first.handle_end(), second.handle_start()), - after_id: bezier_id.end, + let manipulator_group = bezier_rs::ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start()); + let insert = GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start }, }; + responses.add(insert); // Adjust the last manipulator group's in handle - let in_handle = Operation::SetManipulatorPoints { - layer_path: layer_path.clone(), - id: bezier_id.end, - manipulator_type: ManipulatorType::InHandle, - position: second.handle_end().map(|p| p.into()), + let point = ManipulatorPointId::new(end, SelectedType::InHandle); + let position = second.handle_end().unwrap_or(second.end()); + let in_handle = GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, }; + responses.add(in_handle); - responses.extend([out_handle.into(), insert.into(), in_handle.into()]); return; } } @@ -462,53 +435,57 @@ impl ShapeEditor { /// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool). pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque) -> bool { let mut process_layer = |layer_path| { - let manipulator_groups = document.layer(layer_path).ok()?.as_subpath()?.manipulator_groups(); + let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; let mut result = None; let mut closest_distance_squared = tolerance * tolerance; // Find the closest anchor point on the current layer - for (index, (&bezier_id, group)) in manipulator_groups.enumerate().enumerate() { - if let Some(anchor) = &group.points[ManipulatorType::Anchor as usize] { - let screenspace = transform_to_screenspace.transform_point2(anchor.position); + for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() { + for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() { + let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor); let distance_squared = screenspace.distance_squared(position); if distance_squared < closest_distance_squared { closest_distance_squared = distance_squared; - result = Some((anchor.position, index, bezier_id, group)); + result = Some((subpath_index, manipulator_index, manipulator)); } } } - let (anchor_position, index, bezier_id, group) = result?; + let (subpath_index, index, manipulator) = result?; + let anchor_position = manipulator.anchor; + + let subpath = &vector_data.subpaths[subpath_index]; // Check by comparing the handle positions to the anchor if this maniuplator group is a point - let already_sharp = match &group.points { - [_, Some(in_handle), Some(out_handle)] => anchor_position.abs_diff_eq(in_handle.position, f64::EPSILON * 100.) && anchor_position.abs_diff_eq(out_handle.position, f64::EPSILON * 100.), - [_, Some(handle), None] | [_, None, Some(handle)] => anchor_position.abs_diff_eq(handle.position, f64::EPSILON * 100.), - [_, None, None] => true, + let already_sharp = match (manipulator.in_handle, manipulator.out_handle) { + (Some(in_handle), Some(out_handle)) => anchor_position.abs_diff_eq(in_handle, 1e-10) && anchor_position.abs_diff_eq(out_handle, 1e-10), + (Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10), + (None, None) => true, }; + let manipulator_groups = subpath.manipulator_groups(); let (in_handle, out_handle) = if already_sharp { - let is_closed = manipulator_groups.last().filter(|group| group.is_close()).is_some(); + let is_closed = subpath.closed(); // Grab the next and previous manipulator groups by simply looking at the next / previous index - let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.by_index(index)).and_then(|group| group.points[0].as_ref()); - let mut next_position = manipulator_groups.by_index(index + 1).and_then(|group| group.points[0].as_ref()); + let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.get(index)).map(|group| group.anchor); + let mut next_position = manipulator_groups.get(index + 1).map(|group| group.anchor); - // Wrapping around closed path (assuming format is point elements then a single close path) + // Wrapping around closed path if is_closed { - previous_position = previous_position.or_else(|| manipulator_groups.iter().nth_back(1).and_then(|group| group.points[0].as_ref())); - next_position = next_position.or_else(|| manipulator_groups.first().and_then(|group| group.points[0].as_ref())); + previous_position = previous_position.or_else(|| manipulator_groups.last().map(|group| group.anchor)); + next_position = next_position.or_else(|| manipulator_groups.first().map(|group| group.anchor)); } // To find the length of the new tangent we just take the distance to the anchor and divide by 3 (pretty arbitrary) - let length_previous = previous_position.map(|point| (point.position - anchor_position).length() / 3.); - let length_next = next_position.map(|point| (point.position - anchor_position).length() / 3.); + let length_previous = previous_position.map(|point| (point - anchor_position).length() / 3.); + let length_next = next_position.map(|point| (point - anchor_position).length() / 3.); // Use the position relative to the anchor - let previous_angle = previous_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x)); - let next_angle = next_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x)); + let previous_angle = previous_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x)); + let next_angle = next_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x)); // The direction of the handles is either the perpendicular vector to the sum of the anchors' positions or just the anchor's position (if only one) let handle_direction = match (previous_angle, next_angle) { @@ -519,21 +496,20 @@ impl ShapeEditor { }; // Mirror the angle but not the distance - responses.push_back( - Operation::SetManipulatorHandleMirroring { - layer_path: layer_path.to_vec(), - id: bezier_id, + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorHandleMirroring { + id: manipulator.id, mirror_angle: true, - } - .into(), - ); + }, + }); let (sin, cos) = handle_direction.sin_cos(); let mut handle_vector = DVec2::new(cos, sin); // Flip the vector if it is not facing towards the same direction as the anchor - if previous_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) < 0.).is_some() - || next_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) > 0.).is_some() + if previous_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) < 0.).is_some() + || next_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) > 0.).is_some() { handle_vector = -handle_vector; } @@ -548,34 +524,26 @@ impl ShapeEditor { // Push both in and out handles into the correct position if let Some(in_handle) = in_handle { - let in_handle = Operation::SetManipulatorPoints { - layer_path: layer_path.to_vec(), - id: bezier_id, - manipulator_type: ManipulatorType::InHandle, - position: Some(in_handle.into()), - }; - responses.push_back(in_handle.into()); + let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle }, + }); } if let Some(out_handle) = out_handle { - let out_handle = Operation::SetManipulatorPoints { - layer_path: layer_path.to_vec(), - id: bezier_id, - manipulator_type: ManipulatorType::OutHandle, - position: Some(out_handle.into()), - }; - responses.push_back(out_handle.into()); + let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle }, + }); } Some(true) }; - for layer_path in &self.selected_layers { + for layer_path in self.selected_shape_state.keys() { if let Some(result) = process_layer(layer_path) { return result; } } false } - - fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> { - document.layer(layer_id).ok()?.as_subpath() - } } diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 6a32a09327..110e7714fe 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -6,10 +6,10 @@ use crate::consts::{ }; use crate::messages::prelude::*; -use document_legacy::layers::layer_info::{Layer, LayerDataType}; +use document_legacy::layers::layer_info::Layer; use document_legacy::layers::style::{self, Stroke}; use document_legacy::{LayerId, Operation}; -use graphene_std::vector::consts::ManipulatorType; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::{DAffine2, DVec2}; use std::f64::consts::PI; @@ -260,35 +260,26 @@ impl SnapManager { include_handles: bool, ignore_points: &[ManipulatorPointInfo], ) { - if let LayerDataType::Shape(shape_layer) = &layer.data { - let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap(); - let snap_points = shape_layer - .shape - .manipulator_groups() - .enumerate() - .flat_map(|(id, shape)| { - if include_handles { - [ - (*id, &shape.points[ManipulatorType::Anchor]), - (*id, &shape.points[ManipulatorType::InHandle]), - (*id, &shape.points[ManipulatorType::OutHandle]), - ] - } else { - [(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)] - } - }) - .filter_map(|(id, point)| point.as_ref().map(|val| (id, val))) - .filter(|(id, point)| { - !ignore_points.contains(&ManipulatorPointInfo { - shape_layer_path: path, - manipulator_group_id: *id, - manipulator_type: point.manipulator_type, - }) - }) - .map(|(_id, point)| DVec2::new(point.position.x, point.position.y)) - .map(|pos| transform.transform_point2(pos)); - self.add_snap_points(document_message_handler, input, snap_points); - } + let Some(vector_data) = &layer.as_vector_data() else { return }; + + let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap(); + let snap_points = vector_data + .manipulator_groups() + .flat_map(|group| { + if include_handles { + [ + Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), + group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)), + group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)), + ] + } else { + [Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None] + } + }) + .flatten() + .filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id })) + .map(|(_, pos)| transform.transform_point2(pos)); + self.add_snap_points(document_message_handler, input, snap_points); } /// Adds all of the shape handles in the document, including bézier handles of the points specified diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 15acbeccd0..6c32129e4a 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,4 +1,6 @@ -use super::utility_types::{tool_message_to_tool_type, ToolFsmState}; +use super::common_functionality::overlay_renderer::OverlayRenderer; +use super::common_functionality::shape_editor::ShapeState; +use super::utility_types::{tool_message_to_tool_type, ToolActionHandlerData, ToolFsmState}; use crate::application::generate_uuid; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::layout::utility_types::misc::LayoutTarget; @@ -11,8 +13,10 @@ use graphene_core::raster::color::Color; #[derive(Debug, Default)] pub struct ToolMessageHandler { - tool_state: ToolFsmState, - transform_layer_handler: TransformLayerMessageHandler, + pub tool_state: ToolFsmState, + pub transform_layer_handler: TransformLayerMessageHandler, + pub shape_overlay: OverlayRenderer, + pub shape_editor: ShapeState, } impl MessageHandler for ToolMessageHandler { @@ -31,7 +35,7 @@ impl MessageHandler self .transform_layer_handler - .process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data)), + .process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data, &mut self.shape_editor)), #[remain::unsorted] ToolMessage::ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()), @@ -70,7 +74,6 @@ impl MessageHandler { let tool_data = &mut self.tool_state.tool_data; - let document_data = &self.tool_state.document_tool_data; let old_tool = tool_data.active_tool_type; // Do nothing if switching to the same tool @@ -81,13 +84,26 @@ impl MessageHandler { let tool_data = &mut self.tool_state.tool_data; @@ -210,12 +232,28 @@ impl MessageHandler self.tool_state.tool_data.active_tool_type, tool_message => tool_message_to_tool_type(tool_message), }; - let document_data = &self.tool_state.document_tool_data; let tool_data = &mut self.tool_state.tool_data; if let Some(tool) = tool_data.tools.get_mut(&tool_type) { if tool_type == tool_data.active_tool_type { - tool.process_message(tool_message, responses, (document, document_id, document_data, input, &render_data)); + let mut data = ToolActionHandlerData { + document, + document_id, + global_tool_data: &self.tool_state.document_tool_data, + input, + render_data: &render_data, + shape_overlay: &mut self.shape_overlay, + shape_editor: &mut self.shape_editor, + }; + if matches!(tool_message, ToolMessage::UpdateHints) { + if self.transform_layer_handler.is_transforming() { + self.transform_layer_handler.hints(responses); + } else { + tool.process_message(ToolMessage::UpdateHints, responses, &mut data) + } + } else { + tool.process_message(tool_message, responses, &mut data); + } } } } diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 1626d9b727..a3959505d3 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -58,8 +58,8 @@ impl ToolMetadata for ArtboardTool { } } -impl<'a> MessageHandler> for ArtboardTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for ArtboardTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false); } @@ -112,7 +112,7 @@ impl Fsm for ArtboardToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index 10bda09d97..9de56b6b33 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -1,15 +1,16 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; -use document_legacy::Operation; +use graphene_core::vector::style::Fill; -use glam::DAffine2; +use glam::DVec2; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -49,8 +50,8 @@ impl ToolMetadata for EllipseTool { impl PropertyHolder for EllipseTool {} -impl<'a> MessageHandler> for EllipseTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for EllipseTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); } @@ -100,7 +101,13 @@ impl Fsm for EllipseToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -114,18 +121,32 @@ impl Fsm for EllipseToolFsmState { (Ready, DragStart) => { shape_data.start(responses, document, input, render_data); responses.push_back(DocumentMessage::StartTransaction.into()); - shape_data.path = Some(document.get_path_for_new_layer()); - responses.push_back(DocumentMessage::DeselectAllLayers.into()); - responses.push_back( - Operation::AddEllipse { - path: shape_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)), - } - .into(), - ); + // Create a new layer path for this shape + let layer_path = document.get_path_for_new_layer(); + shape_data.path = Some(layer_path.clone()); + + // Create a new ellipse vector shape + let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE); + let manipulator_groups = subpath.manipulator_groups().to_vec(); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + + // Set the four manipulator groups to have their handle angles mirrored by default + for manipulator_group in manipulator_groups { + responses.add(GraphOperationMessage::Vector { + layer: layer_path.clone(), + modification: VectorDataModification::SetManipulatorHandleMirroring { + id: manipulator_group.id, + mirror_angle: true, + }, + }); + } + + // Set the fill color to the primary working color + responses.add(GraphOperationMessage::FillSet { + layer: layer_path, + fill: Fill::solid(global_tool_data.primary_color), + }); Drawing } diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index d7b08b472b..4b28642fc3 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -43,8 +43,8 @@ impl ToolMetadata for EyedropperTool { impl PropertyHolder for EyedropperTool {} -impl<'a> MessageHandler> for EyedropperTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for EyedropperTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); } @@ -87,7 +87,7 @@ impl Fsm for EyedropperToolFsmState { self, event: ToolMessage, _tool_data: &mut Self::ToolData, - (_document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData, + ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 6b420872ec..98406b1808 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -8,7 +8,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::intersection::Quad; use document_legacy::layers::style::Fill; -use document_legacy::Operation; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -46,8 +45,8 @@ impl ToolMetadata for FillTool { impl PropertyHolder for FillTool {} -impl<'a> MessageHandler> for FillTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for FillTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); } @@ -84,7 +83,13 @@ impl Fsm for FillToolFsmState { self, event: ToolMessage, _tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -106,9 +111,12 @@ impl Fsm for FillToolFsmState { }; let fill = Fill::Solid(color); - responses.push_back(DocumentMessage::StartTransaction.into()); - responses.push_back(Operation::SetLayerFill { path: path.to_vec(), fill }.into()); - responses.push_back(DocumentMessage::CommitTransaction.into()); + responses.add(DocumentMessage::StartTransaction); + responses.add(DocumentMessage::SetSelectedLayers { + replacement_selected_layers: vec![path.to_vec()], + }); + responses.add(GraphOperationMessage::FillSet { layer: path.to_vec(), fill }); + responses.add(DocumentMessage::CommitTransaction); } Ready diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index cf4c937076..3b662c6e5a 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -3,14 +3,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; use document_legacy::LayerId; use document_legacy::Operation; +use graphene_core::vector::style::Stroke; -use glam::{DAffine2, DVec2}; +use glam::DVec2; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -82,8 +83,8 @@ impl PropertyHolder for FreehandTool { } } -impl<'a> MessageHandler> for FreehandTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for FreehandTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message { match action { FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -137,7 +138,9 @@ impl Fsm for FreehandToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, global_tool_data, input, .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -159,7 +162,7 @@ impl Fsm for FreehandToolFsmState { tool_data.weight = tool_options.line_weight; - responses.push_back(add_polyline(tool_data, global_tool_data)); + add_polyline(tool_data, global_tool_data, responses); Drawing } @@ -170,15 +173,14 @@ impl Fsm for FreehandToolFsmState { tool_data.points.push(pos); } - responses.push_back(remove_preview(tool_data)); - responses.push_back(add_polyline(tool_data, global_tool_data)); + add_polyline(tool_data, global_tool_data, responses); Drawing } (Drawing, DragStop) | (Drawing, Abort) => { if tool_data.points.len() >= 2 { responses.push_back(remove_preview(tool_data)); - responses.push_back(add_polyline(tool_data, global_tool_data)); + add_polyline(tool_data, global_tool_data, responses); responses.push_back(DocumentMessage::CommitTransaction.into()); } else { responses.push_back(DocumentMessage::AbortTransaction.into()); @@ -214,15 +216,13 @@ fn remove_preview(data: &FreehandToolData) -> Message { Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() } -fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData) -> Message { - let points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect(); +fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque) { + let layer_path = data.path.clone().unwrap(); + let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); - Operation::AddPolyline { - path: data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::IDENTITY.to_cols_array(), - points, - style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None), - } - .into() + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path, + stroke: Stroke::new(tool_data.primary_color, data.weight), + }); } diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 3022bbffca..d4e9580bb0 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -75,8 +75,8 @@ impl ToolMetadata for GradientTool { } } -impl<'a> MessageHandler> for GradientTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for GradientTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message { match action { GradientOptionsUpdate::Type(gradient_type) => { @@ -353,8 +353,8 @@ impl SelectedGradient { pub fn render_gradient(&mut self, responses: &mut VecDeque) { self.gradient.transform = self.transform; let fill = Fill::Gradient(self.gradient.clone()); - let path = self.path.clone(); - responses.push_back(Operation::SetLayerFill { path, fill }.into()); + let layer = self.path.clone(); + responses.add(GraphOperationMessage::FillSet { layer, fill }); } } @@ -396,7 +396,13 @@ impl Fsm for GradientToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -450,8 +456,8 @@ impl Fsm for GradientToolFsmState { // The gradient has only one point and so should become a fill if selected_gradient.gradient.positions.len() == 1 { let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK)); - let path = selected_gradient.path.clone(); - responses.push_back(Operation::SetLayerFill { path, fill }.into()); + let layer = selected_gradient.path.clone(); + responses.add(GraphOperationMessage::FillSet { layer, fill }); return self; } diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index ca7a8f2bff..288bcbc859 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -37,8 +37,8 @@ pub enum ImaginateToolMessage { impl PropertyHolder for ImaginateTool {} -impl<'a> MessageHandler> for ImaginateTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for ImaginateTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } @@ -100,7 +100,7 @@ impl Fsm for ImaginateToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 692a1b318c..f3e134d0dd 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -5,15 +5,15 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; use document_legacy::LayerId; -use document_legacy::Operation; +use graphene_core::vector::style::Stroke; -use glam::{DAffine2, DVec2}; +use glam::DVec2; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -82,8 +82,8 @@ impl PropertyHolder for LineTool { } } -impl<'a> MessageHandler> for LineTool { - fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for LineTool { + fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message { match action { LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -137,7 +137,13 @@ impl Fsm for LineToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -151,22 +157,19 @@ impl Fsm for LineToolFsmState { tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X); + responses.push_back(DocumentMessage::StartTransaction.into()); - tool_data.path = Some(document.get_path_for_new_layer()); - responses.push_back(DocumentMessage::DeselectAllLayers.into()); + let layer_path = document.get_path_for_new_layer(); + tool_data.path = Some(layer_path.clone()); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path.clone(), + stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight), + }); tool_data.weight = tool_options.line_weight; - responses.push_back( - Operation::AddLine { - path: tool_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None), - } - .into(), - ); - Drawing } (Drawing, Redraw { center, snap_angle, lock_angle }) => { @@ -249,9 +252,10 @@ fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle line_length *= 2.; } - Operation::SetLayerTransformInViewport { - path: tool_data.path.clone().unwrap(), - transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(), + GraphOperationMessage::TransformSet { + layer: tool_data.path.clone().unwrap(), + transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start), + transform_in: TransformIn::Viewport, } .into() } diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 107753c996..f90e52a28c 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -50,8 +50,8 @@ impl ToolMetadata for NavigateTool { impl PropertyHolder for NavigateTool {} -impl<'a> MessageHandler> for NavigateTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for NavigateTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } @@ -105,7 +105,7 @@ impl Fsm for NavigateToolFsmState { self, message: ToolMessage, tool_data: &mut Self::ToolData, - (_document, _document_id, _global_tool_data, input, _render_data): ToolActionHandlerData, + ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, messages: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs index ebb390f751..ddbd5e745d 100644 --- a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs @@ -37,8 +37,8 @@ pub enum NodeGraphFrameToolMessage { impl PropertyHolder for NodeGraphFrameTool {} -impl<'a> MessageHandler> for NodeGraphFrameTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for NodeGraphFrameTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } @@ -100,7 +100,7 @@ impl Fsm for NodeGraphToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index c9c030f2b1..0a96d535a4 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -4,13 +4,14 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; -use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeEditor}; +use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeState}; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use document_legacy::intersection::Quad; use document_legacy::LayerId; -use graphene_std::vector::consts::ManipulatorType; +use graphene_core::uuid::ManipulatorGroupId; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -62,8 +63,8 @@ impl ToolMetadata for PathTool { impl PropertyHolder for PathTool {} -impl<'a> MessageHandler> for PathTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for PathTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } @@ -106,14 +107,28 @@ enum PathToolFsmState { #[derive(Default)] struct PathToolData { - shape_editor: ShapeEditor, - overlay_renderer: OverlayRenderer, snap_manager: SnapManager, drag_start_pos: DVec2, previous_mouse_position: DVec2, alt_debounce: bool, - opposing_handle_lengths: Option, HashMap>>, + opposing_handle_lengths: Option, HashMap>>, +} + +impl PathToolData { + fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque) { + // Set the previously selected layers to invisible + for layer_path in document.all_layers() { + shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); + } + + // Render the new overlays + for layer_path in shape_editor.selected_shape_state.keys() { + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); + } + + self.opposing_handle_lengths = None; + } } impl Fsm for PathToolFsmState { @@ -124,27 +139,25 @@ impl Fsm for PathToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + input, + render_data, + shape_editor, + shape_overlay, + .. + }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { if let ToolMessage::Path(event) = event { match (self, event) { (_, PathToolMessage::SelectionChanged) => { - // Set the previously selected layers to invisible - for layer_path in document.all_layers() { - tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); - } - // Set the newly targeted layers to visible let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); - tool_data.shape_editor.set_selected_layers(layer_paths); - // Render the new overlays - for layer_path in tool_data.shape_editor.selected_layers() { - tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } + shape_editor.set_selected_layers(layer_paths); - tool_data.opposing_handle_lengths = None; + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); // This can happen in any state (which is why we return self) self } @@ -152,7 +165,7 @@ impl Fsm for PathToolFsmState { // When the document has moved / needs to be redraw, re-render the overlays // TODO the overlay system should probably receive this message instead of the tool for layer_path in document.selected_visible_layers() { - tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); } self @@ -162,39 +175,39 @@ impl Fsm for PathToolFsmState { let shift_pressed = input.keyboard.get(add_to_selection as usize); tool_data.opposing_handle_lengths = None; + let selected_layers = shape_editor.selected_layers().cloned().collect(); // Select the first point within the threshold (in pixels) - if let Some(mut selected_points) = tool_data - .shape_editor - .select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed, responses) - { + if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) { responses.push_back(DocumentMessage::StartTransaction.into()); - let ignore_document = tool_data.shape_editor.selected_layers().clone(); tool_data .snap_manager - .start_snap(document, input, document.bounding_boxes(Some(&ignore_document), None, render_data), true, true); + .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true); // Do not snap against handles when anchor is selected - let mut extension = Vec::new(); + let mut additional_selected_points = Vec::new(); for point in selected_points.points.iter() { - if point.manipulator_type == ManipulatorType::Anchor { - extension.push(ManipulatorPointInfo { - manipulator_type: ManipulatorType::InHandle, - ..*point + if point.point_id.manipulator_type == SelectedType::Anchor { + additional_selected_points.push(ManipulatorPointInfo { + shape_layer_path: point.shape_layer_path, + point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle), }); - extension.push(ManipulatorPointInfo { - manipulator_type: ManipulatorType::OutHandle, - ..*point + additional_selected_points.push(ManipulatorPointInfo { + shape_layer_path: point.shape_layer_path, + point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle), }); } } - selected_points.points.extend(extension); + selected_points.points.extend(additional_selected_points); - let include_handles = tool_data.shape_editor.selected_layers_ref(); + let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect(); tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points); tool_data.drag_start_pos = input.mouse.position; tool_data.previous_mouse_position = input.mouse.position - selected_points.offset; + + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + PathToolFsmState::Dragging } // We didn't find a point nearby, so consider selecting the nearest shape instead @@ -219,7 +232,7 @@ impl Fsm for PathToolFsmState { tool_data.drag_start_pos = input.mouse.position; tool_data.previous_mouse_position = input.mouse.position; // Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer. - tool_data.shape_editor.select_all_anchors(responses, top_most_intersection); + shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection); return PathToolFsmState::Dragging; } } else { @@ -241,51 +254,47 @@ impl Fsm for PathToolFsmState { ) => { // Determine when alt state changes let alt_pressed = input.keyboard.get(alt_mirror_angle as usize); - if alt_pressed != tool_data.alt_debounce { - tool_data.alt_debounce = alt_pressed; - // Only on alt down - if alt_pressed { - tool_data.opposing_handle_lengths = None; - tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, responses); - } + + // Only on alt down + if alt_pressed && !tool_data.alt_debounce { + tool_data.opposing_handle_lengths = None; + shape_editor.toggle_handle_mirroring_on_selected(responses); } + tool_data.alt_debounce = alt_pressed; // Determine when shift state changes let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); if shift_pressed { if tool_data.opposing_handle_lengths.is_none() { - tool_data.opposing_handle_lengths = Some(tool_data.shape_editor.opposing_handle_lengths(&document.document_legacy)); + tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy)); } } else { if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths { - tool_data.shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses); + shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses); tool_data.opposing_handle_lengths = None; } } // Move the selected points by the mouse position let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - tool_data - .shape_editor - .move_selected_points(snapped_position - tool_data.previous_mouse_position, shift_pressed, responses); + shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses); tool_data.previous_mouse_position = snapped_position; PathToolFsmState::Dragging } // Mouse up (_, PathToolMessage::DragStop { shift_mirror_distance }) => { - let selected_points = tool_data.shape_editor.selected_points(&document.document_legacy); - let nearest_point = tool_data.shape_editor.find_nearest_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD); + let nearest_point = shape_editor + .find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD) + .map(|(_, nearest_point)| nearest_point) + .clone(); let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed { - for point in selected_points { - if nearest_point == Some(point) { - responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into()); - tool_data - .shape_editor - .select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false, responses); - } + let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point)); + if clicked_selected { + shape_editor.deselect_all(); + shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false); } } @@ -296,18 +305,18 @@ impl Fsm for PathToolFsmState { (_, PathToolMessage::Delete) => { // Delete the selected points and clean up overlays responses.push_back(DocumentMessage::StartTransaction.into()); - tool_data.shape_editor.delete_selected_points(responses); + shape_editor.delete_selected_points(responses); responses.push_back(PathToolMessage::SelectionChanged.into()); for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); } PathToolFsmState::Ready } (_, PathToolMessage::InsertPoint) => { // First we try and flip the sharpness (if they have clicked on an anchor) - if !tool_data.shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) { + if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) { // If not, then we try and split the path that may have been clicked upon - tool_data.shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses); + shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses); } self @@ -315,7 +324,7 @@ impl Fsm for PathToolFsmState { (_, PathToolMessage::Abort) => { // TODO Tell overlay manager to remove the overlays for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); } PathToolFsmState::Ready } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index f182261438..2676f6c664 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -3,17 +3,20 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; + use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; use document_legacy::LayerId; -use document_legacy::Operation; -use graphene_std::vector::consts::ManipulatorType; -use graphene_std::vector::manipulator_group::ManipulatorGroup; +use graphene_core::uuid::ManipulatorGroupId; +use graphene_core::vector::style::Stroke; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; +use graphene_core::Color; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -98,8 +101,8 @@ impl PropertyHolder for PenTool { } } -impl<'a> MessageHandler> for PenTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for PenTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message { match action { PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -139,18 +142,342 @@ impl ToolTransition for PenTool { } } } - +struct ModifierState { + snap_angle: bool, + lock_angle: bool, + break_handle: bool, +} #[derive(Clone, Debug, Default)] struct PenToolData { weight: f64, path: Option>, - overlay_renderer: OverlayRenderer, + subpath_index: usize, snap_manager: SnapManager, should_mirror: bool, // Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point from_start: bool, angle: f64, } +impl PenToolData { + fn extend_subpath(&mut self, layer: &[LayerId], subpath_index: usize, from_start: bool, document: &DocumentMessageHandler, responses: &mut VecDeque) { + self.path = Some(layer.to_vec()); + self.from_start = from_start; + self.subpath_index = subpath_index; + + // Stop the handles on the first point from mirroring + let Some(vector_data) = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_vector_data()) else { return }; + let manipulator_groups = vector_data.subpaths[subpath_index].manipulator_groups(); + let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { return }; + + responses.add(GraphOperationMessage::Vector { + layer: layer.to_vec(), + modification: VectorDataModification::SetManipulatorHandleMirroring { + id: last_handle.id, + mirror_angle: false, + }, + }); + } + fn create_new_path(&mut self, document: &DocumentMessageHandler, line_weight: f64, color: Color, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + // Deselect layers because we are now creating a new layer + responses.add(DocumentMessage::DeselectAllLayers); + + let layer_path = document.get_path_for_new_layer(); + + // Get the position and set properties + let transform = document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default(); + let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position); + let start_position = transform.inverse().transform_point2(snapped_position); + self.weight = line_weight; + + // Create the initial shape with a `bez_path` (only contains a moveto initially) + let subpath = bezier_rs::Subpath::new(vec![bezier_rs::ManipulatorGroup::new(start_position, Some(start_position), Some(start_position))], false); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path.clone(), + stroke: Stroke::new(color, line_weight), + }); + + self.path = Some(layer_path); + self.from_start = false; + self.subpath_index = 0; + } + /// If you place the anchor on top of the previous anchor then you break the mirror + /// + /// TODO: tooltip / user documentation? + fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque) -> Option<()> { + // Get subpath + let layer_path = self.path.as_ref()?; + let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?; + let subpath = &vector_data.subpaths[self.subpath_index]; + + // Get the last manipulator group and the one previous to that + let mut manipulator_groups = subpath.manipulator_groups().iter(); + let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + + // Get correct handle types + let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle }; + + // Get manipulator points + let last_anchor = last_manipulator_group.anchor; + let previous_anchor = previous_manipulator_group.anchor; + + // Break the control + let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2); + if !on_top { + return None; + } + // Remove the point that has just been placed + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id }, + }); + + // Move the in handle of the previous anchor to on top of the previous position + let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor }, + }); + + // Stop the handles on the last point from mirroring + let id = previous_manipulator_group.id; + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false }, + }); + + // The overlay system cannot detect deleted points so we must just delete all the overlays + for layer_path in document.all_layers() { + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + } + + self.should_mirror = false; + None + } + + fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque) -> Option { + // Get subpath + let layer_path = self.path.as_ref()?; + let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?; + let subpath = &vector_data.subpaths[self.subpath_index]; + + // Get the last manipulator group and the one previous to that + let mut manipulator_groups = subpath.manipulator_groups().iter(); + let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; + + // Get the first manipulator group + let first_manipulator_group = if self.from_start { + subpath.manipulator_groups().last()? + } else { + subpath.manipulator_groups().first()? + }; + + // Get correct handle types + let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle }; + let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle }; + + // Get manipulator points + let last_anchor = last_manipulator_group.anchor; + let first_anchor = first_manipulator_group.anchor; + let last_in = inwards_handle.get_position(last_manipulator_group)?; + + let transformed_distance_between_squared = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(first_anchor)); + let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); + let should_close_path = transformed_distance_between_squared < snap_point_tolerance_squared && previous_manipulator_group.is_some(); + if should_close_path { + // Move the in handle of the first point to where the user has placed it + let point = ManipulatorPointId::new(first_manipulator_group.id, inwards_handle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: last_in }, + }); + + // Stop the handles on the first point from mirroring + let id = first_manipulator_group.id; + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false }, + }); + + // Remove the point that has just been placed + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id }, + }); + + // Push a close path node + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetClosed { index: 0, closed: true }, + }); + + responses.push_back(DocumentMessage::CommitTransaction.into()); + + // Clean up overlays + for layer_path in document.all_layers() { + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + } + + // Clean up tool data + self.path = None; + self.snap_manager.cleanup(responses); + + // Return to ready state + return Some(PenToolFsmState::Ready); + } + // Add a new manipulator for the next anchor that we will place + if let Some(out_handle) = outwards_handle.get_position(last_manipulator_group) { + responses.push_back(add_manipulator_group(&self.path, self.from_start, bezier_rs::ManipulatorGroup::new_anchor(out_handle))); + } + + Some(PenToolFsmState::PlacingAnchor) + } + + fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque) -> Option { + // Get subpath + let layer_path = self.path.as_ref()?; + let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?; + let subpath = &vector_data.subpaths[self.subpath_index]; + + // Get the last manipulator group + let manipulator_groups = subpath.manipulator_groups(); + let last_manipulator_group = if self.from_start { manipulator_groups.first()? } else { manipulator_groups.last()? }; + + // Get correct handle types + let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle }; + let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle }; + + // Get manipulator points + let last_anchor = last_manipulator_group.anchor; + + let mouse = self.snap_manager.snap_position(responses, document, mouse); + let pos = transform.inverse().transform_point2(mouse); + + let pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, last_anchor); + + // Update points on current segment (to show preview of new handle) + let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: pos }, + }); + + let should_mirror = !modifiers.break_handle && self.should_mirror; + // Mirror handle of last segment + if should_mirror { + // Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better + let pos = last_anchor - (pos - last_anchor); + let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: pos }, + }); + } + + // Update the mirror status of the currently modifying point + let id = last_manipulator_group.id; + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror }, + }); + + Some(PenToolFsmState::DraggingHandle) + } + + fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque) -> Option { + // Get subpath + let layer_path = self.path.as_ref()?; + let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?; + let subpath = &vector_data.subpaths[self.subpath_index]; + + // Get the last manipulator group and the one previous to that + let mut manipulator_groups = subpath.manipulator_groups().iter(); + let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; + + // Get the first manipulator group + let manipulator_groups = subpath.manipulator_groups(); + let first_manipulator_group = if self.from_start { manipulator_groups.last()? } else { manipulator_groups.first()? }; + + // Get manipulator points + let first_anchor = first_manipulator_group.anchor; + + let mouse = self.snap_manager.snap_position(responses, document, mouse); + let mut pos = transform.inverse().transform_point2(mouse); + + // Snap to the first point (to show close path) + let show_close_path = mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2); + if show_close_path { + pos = first_anchor; + } + + if let Some(relative_previous_anchor) = previous_manipulator_group.map(|group| group.anchor) { + // Snap to the previously placed point (to show break control) + if mouse.distance_squared(transform.transform_point2(relative_previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { + pos = relative_previous_anchor; + } else { + pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, relative_previous_anchor); + } + } + + for manipulator_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] { + let point = ManipulatorPointId::new(last_manipulator_group.id, manipulator_type); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position: pos }, + }); + } + Some(PenToolFsmState::PlacingAnchor) + } + + fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque) -> Option { + // Get subpath + let layer_path = self.path.as_ref()?; + let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?; + let subpath = &vector_data.subpaths[self.subpath_index]; + + // Abort if only one manipulator group has been placed + if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 { + return None; + } + + // Get the last manipulator group and the one previous to that + let mut manipulator_groups = subpath.manipulator_groups().iter(); + let mut last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; + + // Get correct handle types + let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle }; + + // If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted) + let Some(previous_manipulator_group) = previous_manipulator_group else { + return Some(DocumentMessage::AbortTransaction); + }; + + // Clean up if there are two or more manipulators + // Remove the unplaced anchor if in anchor placing mode + if fsm == PenToolFsmState::PlacingAnchor { + let layer_path = layer_path.clone(); + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id }, + }); + last_manipulator_group = previous_manipulator_group; + } + + // Remove the out handle + let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle); + let position = last_manipulator_group.anchor; + responses.add(GraphOperationMessage::Vector { + layer: layer_path.to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); + + return Some(DocumentMessage::CommitTransaction); + } +} impl Fsm for PenToolFsmState { type ToolData = PenToolData; @@ -160,7 +487,15 @@ impl Fsm for PenToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + shape_editor, + shape_overlay, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -172,19 +507,19 @@ impl Fsm for PenToolFsmState { // When the document has moved / needs to be redraw, re-render the overlays // TODO the overlay system should probably receive this message instead of the tool for layer_path in document.selected_visible_layers() { - tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); } self } (_, PenToolMessage::SelectionChanged) => { // Set the previously selected layers to invisible for layer_path in document.all_layers() { - tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); + shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); } // Redraw the overlays of the newly selected layers for layer_path in document.selected_visible_layers() { - tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); } self } @@ -199,383 +534,49 @@ impl Fsm for PenToolFsmState { tool_data.should_mirror = false; // Perform extension of an existing path - if let Some((layer, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) { - tool_data.path = Some(layer.to_vec()); - tool_data.from_start = from_start; - - // Stop the handles on the first point from mirroring - let mut stop_mirror = || { - let subpath = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_subpath())?; - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&id, _) = if from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - - let op = Operation::SetManipulatorHandleMirroring { - layer_path: layer.to_vec(), - id, - mirror_angle: false, - }; - responses.push_back(op.into()); - Some(()) - }; - stop_mirror(); - - return PenToolFsmState::DraggingHandle; - } - - // Deselect layers because we are now creating a new layer - responses.push_back(DocumentMessage::DeselectAllLayers.into()); - - // Create a new layer - tool_data.path = Some(document.get_path_for_new_layer()); - tool_data.from_start = false; - - // Get the position and set properties - let transform = tool_data - .path - .as_ref() - .and_then(|path| document.document_legacy.multiply_transforms(&path[..path.len() - 1]).ok()) - .unwrap_or_default(); - let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let start_position = transform.inverse().transform_point2(snapped_position); - tool_data.weight = tool_options.line_weight; - - // Create the initial shape with a `bez_path` (only contains a moveto initially) - if let Some(layer_path) = &tool_data.path { - responses.push_back( - Operation::AddShape { - path: layer_path.clone(), - transform: DAffine2::IDENTITY.to_cols_array(), - insert_index: -1, - subpath: Default::default(), - style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None), - } - .into(), - ); - responses.push_back(add_manipulator_group( - &tool_data.path, - tool_data.from_start, - ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)), - )); + if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) { + tool_data.extend_subpath(layer, subpath_index, from_start, document, responses); + } else { + tool_data.create_new_path(document, tool_options.line_weight, global_tool_data.primary_color, input, responses); } // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle PenToolFsmState::DraggingHandle } (PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => { - // If you place the anchor on top of the previous anchor then you break the mirror - let mut check_break = || { - // Get subpath - let layer_path = tool_data.path.as_ref()?; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - - // Get the last manipulator group and the one previous to that - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; - - // Get correct handle types - let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle }; - - // Get manipulator points - let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - - if let Some((previous_id, previous_anchor)) = previous - .as_ref() - .and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x))) - { - // Break the control - if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { - // Remove the point that has just been placed - let op = Operation::RemoveManipulatorGroup { - layer_path: layer_path.clone(), - id: last_id, - }; - responses.push_back(op.into()); - - // Move the in handle of the previous anchor to on top of the previous position - let op = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: previous_id, - manipulator_type: outwards_handle, - position: previous_anchor.position.into(), - }; - responses.push_back(op.into()); - - // Stop the handles on the last point from mirroring - let op = Operation::SetManipulatorHandleMirroring { - layer_path: layer_path.clone(), - id: previous_id, - mirror_angle: false, - }; - responses.push_back(op.into()); - - // The overlay system cannot detect deleted points so we must just delete all the overlays - for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } - - tool_data.should_mirror = false; - } - } - None - }; - check_break().unwrap_or(PenToolFsmState::DraggingHandle) + tool_data.check_break(document, transform, shape_overlay, responses); + PenToolFsmState::DraggingHandle } (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => { - let mut process = || { - // Get subpath - let layer_path = tool_data.path.as_ref()?; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - - // Get the last manipulator group and the one previous to that - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; - - // Get the first manipulator group - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? }; - - // Get correct handle types - let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle }; - let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle }; - - // Get manipulator points - let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - let last_in = last_manipulator_group.points[inwards_handle].as_ref()?; - - // Close path - let transformed_distance_between_squared = transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(first_anchor.position)); - let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); - if transformed_distance_between_squared < snap_point_tolerance_squared && previous.is_some() { - // Move the in handle of the first point to where the user has placed it - let op = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: first_id, - manipulator_type: inwards_handle, - position: last_in.position.into(), - }; - responses.push_back(op.into()); - - // Stop the handles on the first point from mirroring - let op = Operation::SetManipulatorHandleMirroring { - layer_path: layer_path.clone(), - id: first_id, - mirror_angle: false, - }; - responses.push_back(op.into()); - - // Remove the point that has just been placed - let op = Operation::RemoveManipulatorGroup { - layer_path: layer_path.clone(), - id: last_id, - }; - responses.push_back(op.into()); - - // Push a close path node - responses.push_back(add_manipulator_group(&tool_data.path, false, ManipulatorGroup::closed())); - - responses.push_back(DocumentMessage::CommitTransaction.into()); - - // Clean up overlays - for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } - - // Clean up tool data - tool_data.path = None; - tool_data.snap_manager.cleanup(responses); - - // Return the new tool state, wrapped in `Some()` because this closure returns an Option used by the `?` operation various times above - return Some(PenToolFsmState::Ready); - } - // Add a new manipulator for the next anchor that we will place - if let Some(out_handle) = &last_manipulator_group.points[outwards_handle] { - responses.push_back(add_manipulator_group(&tool_data.path, tool_data.from_start, ManipulatorGroup::new_with_anchor(out_handle.position))); - } - - // Returning `None` means the `unwrap_or` clause below returns the state `PlacingAnchor` - None - }; tool_data.should_mirror = true; - process().unwrap_or(PenToolFsmState::PlacingAnchor) + tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor) } (PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => { - let mut process = || { - // Get subpath - let layer_path = tool_data.path.as_ref()?; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - - // Get the last manipulator group - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - - // Get correct handle types - let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle }; - let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle }; - - // Get manipulator points - let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - - let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let pos = transform.inverse().transform_point2(mouse); - - let snap_angle = input.keyboard.get(snap_angle as usize); - let lock_angle = input.keyboard.get(lock_angle as usize); - let pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, last_anchor.position); - - // Update points on current segment (to show preview of new handle) - let msg = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: last_id, - manipulator_type: outwards_handle, - position: pos.into(), - }; - responses.push_back(msg.into()); - - let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror; - // Mirror handle of last segment - if should_mirror { - // Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better - let pos = last_anchor.position - (pos - last_anchor.position); - - let msg = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: last_id, - manipulator_type: inwards_handle, - position: pos.into(), - }; - responses.push_back(msg.into()); - } - - // Update the mirror status of the currently modifying point - let op = Operation::SetManipulatorHandleMirroring { - layer_path: layer_path.clone(), - id: last_id, - mirror_angle: should_mirror, - }; - responses.push_back(op.into()); - - Some(()) + let modifiers = ModifierState { + snap_angle: input.keyboard.key(snap_angle), + lock_angle: input.keyboard.key(lock_angle), + break_handle: input.keyboard.key(break_handle), }; - if process().is_none() { - PenToolFsmState::Ready - } else { - self - } + tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready) } - (PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, lock_angle, .. }) => { - let mut process = || { - // Get subpath - let data = tool_data.clone(); - let layer_path = data.path.as_ref()?; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - - // Get the last manipulator group and the one previous to that - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&last_id, _last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; - - // Get the first manipulator group - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (_first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? }; - - // Get manipulator points - let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; - - let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let mut pos = transform.inverse().transform_point2(mouse); - - // Snap to the first point (to show close path) - if mouse.distance_squared(transform.transform_point2(first_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { - pos = first_anchor.position; - } - - if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) { - // Snap to the previously placed point (to show break control) - if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { - pos = relative.position; - } else { - let snap_angle = input.keyboard.get(snap_angle as usize); - let lock_angle = input.keyboard.get(lock_angle as usize); - pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, relative.position); - } - } - - for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] { - let msg = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: last_id, - manipulator_type, - position: pos.into(), - }; - responses.push_back(msg.into()); - } - - Some(()) + (PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => { + let modifiers = ModifierState { + snap_angle: input.keyboard.key(snap_angle), + lock_angle: input.keyboard.key(lock_angle), + break_handle: input.keyboard.key(break_handle), }; - if process().is_none() { - PenToolFsmState::Ready - } else { - self - } + tool_data + .place_anchor(document, transform, input.mouse.position, modifiers, responses) + .unwrap_or(PenToolFsmState::Ready) } (PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => { // Abort or commit the transaction to the undo history - let mut commit = || { - // Get subpath - let layer_path = tool_data.path.as_ref()?; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - - // If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted) - if self == PenToolFsmState::PlacingAnchor && subpath.manipulator_groups().len() < 3 { - return None; - } - - // Get the last manipulator group and the one previous to that - let mut manipulator_groups = subpath.manipulator_groups().enumerate(); - let (&(mut last_id), mut last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; - let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; - - // Get correct handle types - let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle }; - - // Clean up if there are two or more manipulators - if let Some((&previous_id, previous_manipulator_group)) = previous { - // Remove the unplaced anchor if in anchor placing mode - if self == PenToolFsmState::PlacingAnchor { - let layer_path = layer_path.clone(); - let op = Operation::RemoveManipulatorGroup { layer_path, id: last_id }; - responses.push_back(op.into()); - last_id = previous_id; - last_manipulator_group = previous_manipulator_group; - } - - // Remove the out handle - let op = Operation::MoveManipulatorPoint { - layer_path: layer_path.clone(), - id: last_id, - manipulator_type: outwards_handle, - position: last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?.position.into(), - }; - responses.push_back(op.into()); - - responses.push_back(DocumentMessage::CommitTransaction.into()); - - return Some(()); - } - - // Abort if only one manipulator group has been placed - None - }; - if commit().is_none() { - responses.push_back(DocumentMessage::AbortTransaction.into()); - } + let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction); + responses.add(message); // Clean up overlays for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); } tool_data.path = None; tool_data.snap_manager.cleanup(responses); @@ -585,7 +586,7 @@ impl Fsm for PenToolFsmState { (_, PenToolMessage::Abort) => { // Clean up overlays for layer_path in document.all_layers() { - tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); } self } @@ -640,51 +641,44 @@ fn compute_snapped_angle(cached_angle: &mut f64, lock_angle: bool, snap_angle: b } } -/// Pushes a [ManipulatorGroup] to the current layer via an [Operation]. -fn add_manipulator_group(layer_path: &Option>, from_start: bool, manipulator_group: ManipulatorGroup) -> Message { - match (layer_path, from_start) { - (Some(layer_path), true) => Operation::PushFrontManipulatorGroup { - layer_path: layer_path.clone(), - manipulator_group, - } - .into(), - (Some(layer_path), false) => Operation::PushManipulatorGroup { - layer_path: layer_path.clone(), - manipulator_group, - } - .into(), - (None, _) => Message::NoOp, - } +/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage]. +fn add_manipulator_group(layer_path: &Option>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup) -> Message { + let Some(layer) = layer_path.clone() else { + return Message::NoOp; + }; + let modification = if from_start { + VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group } + } else { + VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group } + }; + GraphOperationMessage::Vector { layer, modification }.into() } /// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable. -fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], bool)> { +fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], usize, bool)> { let mut best = None; let mut best_distance_squared = tolerance * tolerance; for layer_path in document.selected_layers() { - (|| { - let viewspace = document.document_legacy.generate_transform_relative_to_viewport(layer_path).ok()?; + let Ok(viewspace) = document.document_legacy.generate_transform_relative_to_viewport(layer_path) else { continue }; - let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; - let (_first_id, first) = subpath.manipulator_groups().enumerate().next()?; - let (_last_id, last) = subpath.manipulator_groups().enumerate().next_back()?; - - if !last.is_close() { - for (manipulator_group, from_start) in [(first, true), (last, false)] { - if let Some(point) = &manipulator_group.points[ManipulatorType::Anchor] { - let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos); - - if distance_squared < best_distance_squared { - best = Some((layer_path, from_start)); - best_distance_squared = distance_squared; - } - } - } + let Some(vector_data) = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data()) else { continue }; + for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() { + if subpath.closed() { + continue; } - None::<()> - })(); + for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] { + let Some(manipulator_group) = manipulator_group else { break }; + + let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos); + + if distance_squared < best_distance_squared { + best = Some((layer_path, subpath_index, from_start)); + best_distance_squared = distance_squared; + } + } + } } best diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 2e1fc37d2e..d563875940 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -2,14 +2,13 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; -use document_legacy::Operation; - -use glam::DAffine2; +use glam::DVec2; +use graphene_core::vector::style::Fill; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -37,8 +36,8 @@ pub enum RectangleToolMessage { impl PropertyHolder for RectangleTool {} -impl<'a> MessageHandler> for RectangleTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for RectangleTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } @@ -100,7 +99,13 @@ impl Fsm for RectangleToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -113,19 +118,17 @@ impl Fsm for RectangleToolFsmState { match (self, event) { (Ready, DragStart) => { shape_data.start(responses, document, input, render_data); - responses.push_back(DocumentMessage::StartTransaction.into()); - shape_data.path = Some(document.get_path_for_new_layer()); - responses.push_back(DocumentMessage::DeselectAllLayers.into()); - responses.push_back( - Operation::AddRect { - path: shape_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)), - } - .into(), - ); + let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE); + + let layer_path = document.get_path_for_new_layer(); + responses.push_back(DocumentMessage::StartTransaction.into()); + shape_data.path = Some(layer_path.clone()); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + responses.add(GraphOperationMessage::FillSet { + layer: layer_path, + fill: Fill::solid(global_tool_data.primary_color), + }); Drawing } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 9aef8ba6a1..6b57fb99ea 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -19,7 +19,6 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::boolean_ops::BooleanOperation; use document_legacy::document::Document; use document_legacy::intersection::Quad; use document_legacy::layers::layer_info::{Layer, LayerDataType}; @@ -219,24 +218,24 @@ impl PropertyHolder for SelectTool { })), Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), IconButton::new("BooleanUnion", 24) - .tooltip("Boolean Union") - .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into()) + .tooltip("Boolean Union (coming soon)") + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .widget_holder(), IconButton::new("BooleanSubtractFront", 24) - .tooltip("Boolean Subtract Front") - .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into()) + .tooltip("Boolean Subtract Front (coming soon)") + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .widget_holder(), IconButton::new("BooleanSubtractBack", 24) - .tooltip("Boolean Subtract Back") - .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into()) + .tooltip("Boolean Subtract Back (coming soon)") + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .widget_holder(), IconButton::new("BooleanIntersect", 24) - .tooltip("Boolean Intersect") - .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into()) + .tooltip("Boolean Intersect (coming soon)") + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .widget_holder(), IconButton::new("BooleanDifference", 24) - .tooltip("Boolean Difference") - .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into()) + .tooltip("Boolean Difference (coming soon)") + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) .widget_holder(), WidgetHolder::related_separator(), PopoverButton::new("Boolean", "Coming soon").widget_holder(), @@ -245,8 +244,8 @@ impl PropertyHolder for SelectTool { } } -impl<'a> MessageHandler> for SelectTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for SelectTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message { self.tool_data.nested_selection_behavior = nested_selection_behavior; responses.push_back(ToolMessage::UpdateHints.into()); @@ -345,9 +344,10 @@ impl SelectToolData { for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) { // Moves the original back to its starting position. responses.push_front( - Operation::TransformLayerInViewport { - path: layer_path.clone(), - transform: DAffine2::from_translation(self.drag_start - self.drag_current).to_cols_array(), + GraphOperationMessage::TransformChange { + layer: layer_path.clone(), + transform: DAffine2::from_translation(self.drag_start - self.drag_current), + transform_in: TransformIn::Viewport, } .into(), ); @@ -401,9 +401,10 @@ impl SelectToolData { // Move the original to under the mouse for layer_path in Document::shallowest_unique_layers(originals.iter()) { responses.push_front( - Operation::TransformLayerInViewport { - path: layer_path.clone(), - transform: DAffine2::from_translation(self.drag_current - self.drag_start).to_cols_array(), + GraphOperationMessage::TransformChange { + layer: layer_path.clone(), + transform: DAffine2::from_translation(self.drag_current - self.drag_start), + transform_in: TransformIn::Viewport, } .into(), ); @@ -429,7 +430,7 @@ impl Fsm for SelectToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -619,9 +620,10 @@ impl Fsm for SelectToolFsmState { // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) { responses.push_front( - Operation::TransformLayerInViewport { - path: path.to_vec(), - transform: DAffine2::from_translation(mouse_delta + closest_move).to_cols_array(), + GraphOperationMessage::TransformChange { + layer: path.to_vec(), + transform: DAffine2::from_translation(mouse_delta + closest_move), + transform_in: TransformIn::Viewport, } .into(), ); diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 28c66511d4..90651602d2 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -3,14 +3,13 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; -use document_legacy::Operation; - -use glam::DAffine2; +use glam::DVec2; +use graphene_core::vector::style::Fill; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -80,8 +79,8 @@ impl PropertyHolder for ShapeTool { } } -impl<'a> MessageHandler> for ShapeTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for ShapeTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message { match action { ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, @@ -139,7 +138,13 @@ impl Fsm for ShapeToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -153,20 +158,16 @@ impl Fsm for ShapeToolFsmState { (Ready, DragStart) => { shape_data.start(responses, document, input, render_data); responses.push_back(DocumentMessage::StartTransaction.into()); - shape_data.path = Some(document.get_path_for_new_layer()); - responses.push_back(DocumentMessage::DeselectAllLayers.into()); + let layer_path = document.get_path_for_new_layer(); + shape_data.path = Some(layer_path.clone()); tool_data.sides = tool_options.vertices; - responses.push_back( - Operation::AddNgon { - path: shape_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - sides: tool_data.sides, - style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)), - } - .into(), - ); + let subpath = bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_data.sides as u64, 1.); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + responses.add(GraphOperationMessage::FillSet { + layer: layer_path, + fill: Fill::solid(global_tool_data.primary_color), + }); Drawing } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 394a9ea781..c37abab028 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -4,15 +4,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use document_legacy::layers::style; -use document_legacy::LayerId; -use document_legacy::Operation; +use document_legacy::{LayerId, Operation}; +use graphene_core::vector::style::Stroke; -use glam::{DAffine2, DVec2}; +use glam::DVec2; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -86,8 +86,8 @@ impl PropertyHolder for SplineTool { } } -impl<'a> MessageHandler> for SplineTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for SplineTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message { match action { SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -146,7 +146,13 @@ impl Fsm for SplineToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -173,7 +179,7 @@ impl Fsm for SplineToolFsmState { tool_data.weight = tool_options.line_weight; - responses.push_back(add_spline(tool_data, global_tool_data, true)); + add_spline(tool_data, global_tool_data, true, responses); Drawing } @@ -189,7 +195,7 @@ impl Fsm for SplineToolFsmState { } responses.push_back(remove_preview(tool_data)); - responses.push_back(add_spline(tool_data, global_tool_data, true)); + add_spline(tool_data, global_tool_data, true, responses); Drawing } @@ -199,14 +205,14 @@ impl Fsm for SplineToolFsmState { tool_data.next_point = pos; responses.push_back(remove_preview(tool_data)); - responses.push_back(add_spline(tool_data, global_tool_data, true)); + add_spline(tool_data, global_tool_data, true, responses); Drawing } (Drawing, Confirm) | (Drawing, Abort) => { if tool_data.points.len() >= 2 { responses.push_back(remove_preview(tool_data)); - responses.push_back(add_spline(tool_data, global_tool_data, false)); + add_spline(tool_data, global_tool_data, false, responses); responses.push_back(DocumentMessage::CommitTransaction.into()); } else { responses.push_back(DocumentMessage::AbortTransaction.into()); @@ -249,18 +255,18 @@ fn remove_preview(tool_data: &SplineToolData) -> Message { .into() } -fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool) -> Message { - let mut points: Vec<(f64, f64)> = tool_data.points.iter().map(|p| (p.x, p.y)).collect(); +fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool, responses: &mut VecDeque) { + let mut points = tool_data.points.clone(); if show_preview { - points.push((tool_data.next_point.x, tool_data.next_point.y)) + points.push(tool_data.next_point) } - Operation::AddSpline { - path: tool_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::IDENTITY.to_cols_array(), - points, - style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None), - } - .into() + let subpath = bezier_rs::Subpath::new_cubic_spline(points); + + let layer_path = tool_data.path.clone().unwrap(); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path.clone(), + stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight), + }); } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index c9ad409e22..6446cd3829 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -127,8 +127,8 @@ impl PropertyHolder for TextTool { } } -impl<'a> MessageHandler> for TextTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for TextTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message { match action { TextOptionsUpdate::Font { family, style } => { @@ -266,7 +266,13 @@ impl Fsm for TextToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - (document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData, + ToolActionHandlerData { + document, + global_tool_data, + input, + render_data, + .. + }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -300,7 +306,7 @@ impl Fsm for TextToolFsmState { else if state == TextToolFsmState::Ready { responses.push_back(DocumentMessage::StartTransaction.into()); - let transform = DAffine2::from_translation(input.mouse.position).to_cols_array(); + let transform = DAffine2::from_translation(input.mouse.position); let font_size = tool_options.font_size; let font_name = tool_options.font_name.clone(); let font_style = tool_options.font_style.clone(); @@ -320,9 +326,10 @@ impl Fsm for TextToolFsmState { .into(), ); responses.push_back( - Operation::SetLayerTransformInViewport { - path: tool_data.layer_path.clone(), + GraphOperationMessage::TransformSet { + layer: tool_data.layer_path.clone(), transform, + transform_in: TransformIn::Viewport, } .into(), ); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index f10eb2c814..f84b3500bc 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -2,7 +2,8 @@ use crate::consts::SLOWING_DIVISOR; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing}; use crate::messages::prelude::*; -use crate::messages::tool::common_functionality::shape_editor::ShapeEditor; + +use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::{ToolData, ToolType}; use document_legacy::layers::style::RenderData; @@ -11,7 +12,7 @@ use glam::DVec2; #[derive(Debug, Clone, Default)] pub struct TransformLayerMessageHandler { - transform_operation: TransformOperation, + pub transform_operation: TransformOperation, slow: bool, snap: bool, @@ -22,22 +23,25 @@ pub struct TransformLayerMessageHandler { original_transforms: OriginalTransforms, pivot: DVec2, - - shape_editor: ShapeEditor, +} +impl TransformLayerMessageHandler { + pub fn is_transforming(&self) -> bool { + self.transform_operation != TransformOperation::None + } + pub fn hints(&self, responses: &mut VecDeque) { + self.transform_operation.hints(self.snap, responses); + } } -type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData); +type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData, &'a mut ShapeState); impl<'a> MessageHandler> for TransformLayerMessageHandler { #[remain::check] - fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, ipp, render_data, tool_data): TransformData) { + fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, ipp, render_data, tool_data, shape_editor): TransformData) { use TransformLayerMessage::*; // TODO: Transform individual points when using the path tool. let _using_path_tool = tool_data.active_tool_type == ToolType::Path; - // You may also want the shape editor here? If not, then feel free to remove. - let _shape_editor = &self.shape_editor; - let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::>(); let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, &document.document_legacy); @@ -61,7 +65,8 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + responses.add(ToolMessage::UpdateHints); + responses.add(BroadcastEvent::DocumentIsDirty); } BeginGrab => { if let TransformOperation::Grabbing(_) = self.transform_operation { @@ -123,7 +128,8 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + responses.add(ToolMessage::UpdateHints); + responses.add(BroadcastEvent::DocumentIsDirty); } ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap), ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap), @@ -178,7 +184,7 @@ impl<'a> MessageHandler> for TransformL } SelectionChanged => { let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); - self.shape_editor.set_selected_layers(layer_paths); + shape_editor.set_selected_layers(layer_paths); } TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap), TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap), diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 858da57c2d..ce48ae7b38 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -1,3 +1,5 @@ +use super::common_functionality::overlay_renderer::OverlayRenderer; +use super::common_functionality::shape_editor::ShapeState; use super::tool_messages::*; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion}; use crate::messages::input_mapper::utility_types::macros::action_keys; @@ -15,10 +17,39 @@ use graphene_core::raster::color::Color; use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug}; -pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, u64, &'a DocumentToolData, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>); +pub struct ToolActionHandlerData<'a> { + pub document: &'a DocumentMessageHandler, + pub document_id: u64, + pub global_tool_data: &'a DocumentToolData, + pub input: &'a InputPreprocessorMessageHandler, + pub render_data: &'a RenderData<'a>, + pub shape_overlay: &'a mut OverlayRenderer, + pub shape_editor: &'a mut ShapeState, +} +impl<'a> ToolActionHandlerData<'a> { + pub fn new( + document: &'a DocumentMessageHandler, + document_id: u64, + global_tool_data: &'a DocumentToolData, + input: &'a InputPreprocessorMessageHandler, + render_data: &'a RenderData<'a>, + shape_overlay: &'a mut OverlayRenderer, + shape_editor: &'a mut ShapeState, + ) -> Self { + Self { + document, + document_id, + global_tool_data, + input, + render_data, + shape_overlay, + shape_editor, + } + } +} -pub trait ToolCommon: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} -impl ToolCommon for T where T: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} +pub trait ToolCommon: for<'a, 'b> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} +impl ToolCommon for T where T: for<'a, 'b> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} type Tool = dyn ToolCommon + Send + Sync; @@ -41,7 +72,7 @@ pub trait Fsm { /// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages, /// update some internal tool variables, and end by transitioning to a `Drawing` state. #[must_use] - fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque) -> Self; + fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque) -> Self; /// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar. fn update_hints(&self, responses: &mut VecDeque); @@ -70,7 +101,7 @@ pub trait Fsm { &mut self, message: ToolMessage, tool_data: &mut Self::ToolData, - transition_data: ToolActionHandlerData, + transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque, update_cursor_on_transition: bool, @@ -539,6 +570,16 @@ impl HintInfo { } } + pub fn label(label: impl Into) -> Self { + Self { + key_groups: vec![], + key_groups_mac: None, + mouse: None, + label: label.into(), + plus: false, + } + } + pub fn keys_and_mouse(keys: impl IntoIterator, mouse_motion: MouseMotion, label: impl Into) -> Self { let keys: Vec<_> = keys.into_iter().collect(); Self { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index ba23e950ad..3d4414d8c0 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -6,9 +6,11 @@ use crate::messages::prelude::*; use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType}; use document_legacy::{LayerId, Operation}; +use dyn_any::DynAny; use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput}; use graph_craft::executor::Compiler; use graphene_core::raster::{Image, ImageFrame}; +use graphene_core::vector::VectorData; use interpreted_executor::executor::DynamicExecutor; use glam::{DAffine2, DVec2}; @@ -20,8 +22,8 @@ pub struct NodeGraphExecutor { } impl NodeGraphExecutor { - /// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`. - fn execute_network(&mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result { + /// Execute the network by flattening it and creating a borrow stack. + fn execute_network<'a>(&'a mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result, String> { let mut scoped_network = wrap_network_in_scope(network); scoped_network.duplicate_outputs(&mut generate_uuid); @@ -40,9 +42,7 @@ impl NodeGraphExecutor { use dyn_any::IntoDynAny; use graph_craft::executor::Executor; - let boxed = self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string())?; - - dyn_any::downcast::(boxed).map(|v| *v) + self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string()) } /// Computes an input for a node in the graph @@ -82,7 +82,9 @@ impl NodeGraphExecutor { } } - self.execute_network(network, image_frame.into_owned()) + let boxed = self.execute_network(network, image_frame.into_owned())?; + + dyn_any::downcast::(boxed).map(|v| *v) } /// Encodes an image into a format using the image crate @@ -243,11 +245,24 @@ impl NodeGraphExecutor { }?; let network = node_graph_frame.network.clone(); - // Execute the node graph + // Special execution path for generating imaginate (as generation requires io from outside node graph) if let Some(imaginate_node) = imaginate_node { responses.push_back(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, image_frame, persistent_data)?); + return Ok(()); + } + // Execute the node graph + let boxed_node_graph_output = self.execute_network(network, image_frame)?; + + // Check if the output is vector data + if core::any::TypeId::of::() == DynAny::type_id(boxed_node_graph_output.as_ref()) { + // Update the cached vector data on the layer + let vector_data: VectorData = dyn_any::downcast(boxed_node_graph_output).map(|v| *v)?; + let transform = vector_data.transform.to_cols_array(); + responses.push_back(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into()); + responses.push_back(Operation::SetVectorData { path: layer_path, vector_data }.into()); } else { - let ImageFrame { image, transform } = self.execute_network(network, image_frame)?; + // Attempt to downcast to an image frame + let ImageFrame { image, transform } = dyn_any::downcast(boxed_node_graph_output).map(|image_frame| *image_frame)?; // If no image was generated, clear the frame if image.width == 0 || image.height == 0 { diff --git a/libraries/bezier-rs/src/bezier/core.rs b/libraries/bezier-rs/src/bezier/core.rs index 9fb1c291ca..bc39125982 100644 --- a/libraries/bezier-rs/src/bezier/core.rs +++ b/libraries/bezier-rs/src/bezier/core.rs @@ -123,6 +123,16 @@ impl Bezier { format!("{handle_args} {} {}", self.end.x, self.end.y) } + /// Write the curve argument to the string + pub fn write_curve_argument(&self, svg: &mut String) -> std::fmt::Result { + match self.handles { + BezierHandles::Linear => svg.push_str(SVG_ARG_LINEAR), + BezierHandles::Quadratic { handle } => write!(svg, "{SVG_ARG_QUADRATIC}{},{}", handle.x, handle.y)?, + BezierHandles::Cubic { handle_start, handle_end } => write!(svg, "{SVG_ARG_CUBIC}{},{} {},{}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)?, + } + write!(svg, " {},{}", self.end.x, self.end.y) + } + /// Return the string argument used to create the lines connecting handles to endpoints in an SVG `path` pub(crate) fn svg_handle_line_argument(&self) -> Option { match self.handles { diff --git a/libraries/bezier-rs/src/bezier/solvers.rs b/libraries/bezier-rs/src/bezier/solvers.rs index e222176dff..fa48c45566 100644 --- a/libraries/bezier-rs/src/bezier/solvers.rs +++ b/libraries/bezier-rs/src/bezier/solvers.rs @@ -1,5 +1,5 @@ use super::*; -use crate::utils::TValue; +use crate::utils::{solve_cubic, solve_quadratic, TValue}; use glam::DMat2; use std::ops::Range; @@ -403,6 +403,105 @@ impl Bezier { let handle2 = other.start - other.non_normalized_tangent(0.) / 3.; Bezier::from_cubic_dvec2(self.end, handle1, handle2, other.start) } + + /// Compute the winding order (number of times crossing an infinate line to the left of the point) + /// + /// Assumes curve is split at the extrema. + fn pre_split_winding_number(&self, target_point: DVec2) -> i32 { + // Clockwise is -1, anticlockwise is +1 (with +y as up) + // Looking only to the left (-x) of the target_point + let resulting_sign = if self.end.y > self.start.y { + if target_point.y < self.start.y || target_point.y >= self.end.y { + return 0; + } + -1 + } else if self.end.y < self.start.y { + if target_point.y < self.end.y || target_point.y >= self.start.y { + return 0; + } + 1 + } else { + return 0; + }; + match &self.handles { + BezierHandles::Linear => { + if target_point.x < self.start.x.min(self.end.x) { + return 0; + } + if target_point.x >= self.start.x.max(self.end.x) { + return resulting_sign; + } + // line equation ax + by = c + let a = self.end.y - self.start.y; + let b = self.start.x - self.end.x; + let c = a * self.start.x + b * self.start.y; + if (a * target_point.x + b * target_point.y - c) * (resulting_sign as f64) <= 0.0 { + resulting_sign + } else { + 0 + } + } + BezierHandles::Quadratic { handle: p1 } => { + if target_point.x < self.start.x.min(self.end.x).min(p1.x) { + return 0; + } + if target_point.x >= self.start.x.max(self.end.x).max(p1.x) { + return resulting_sign; + } + let a = self.end.y - 2.0 * p1.y + self.start.y; + let b = 2.0 * (p1.y - self.start.y); + let c = self.start.y - target_point.y; + + let discriminant = b * b - 4. * a * c; + let two_times_a = 2. * a; + for t in solve_quadratic(discriminant, two_times_a, b, c) { + if (0.0..=1.0).contains(&t) { + let x = self.evaluate(TValue::Parametric(t)).x; + if target_point.x >= x { + return resulting_sign; + } else { + return 0; + } + } + } + 0 + } + BezierHandles::Cubic { handle_start: p1, handle_end: p2 } => { + if target_point.x < self.start.x.min(self.end.x).min(p1.x).min(p2.x) { + return 0; + } + if target_point.x >= self.start.x.max(self.end.x).max(p1.x).max(p2.x) { + return resulting_sign; + } + let a = self.end.y - 3.0 * p2.y + 3.0 * p1.y - self.start.y; + let b = 3.0 * (p2.y - 2.0 * p1.y + self.start.y); + let c = 3.0 * (p1.y - self.start.y); + let d = self.start.y - target_point.y; + for t in solve_cubic(a, b, c, d) { + if (0.0..=1.0).contains(&t) { + let x = self.evaluate(TValue::Parametric(t)).x; + if target_point.x >= x { + return resulting_sign; + } else { + return 0; + } + } + } + 0 + } + } + } + + /// Compute the winding number contribution of a single segment. + /// + /// Cast a ray to the left and count intersections. + pub fn winding(&self, target_point: DVec2) -> i32 { + let extrema = self.get_extrema_t_list(); + extrema + .windows(2) + .map(|t| self.trim(TValue::Parametric(t[0]), TValue::Parametric(t[1])).pre_split_winding_number(target_point)) + .sum() + } } #[cfg(test)] diff --git a/libraries/bezier-rs/src/bezier/transform.rs b/libraries/bezier-rs/src/bezier/transform.rs index 7b3f09cd6c..5e2555879e 100644 --- a/libraries/bezier-rs/src/bezier/transform.rs +++ b/libraries/bezier-rs/src/bezier/transform.rs @@ -104,7 +104,7 @@ impl Bezier { } /// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier. - pub fn apply_transformation(&self, transformation_function: &dyn Fn(DVec2) -> DVec2) -> Bezier { + pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier { let transformed_start = transformation_function(self.start); let transformed_end = transformation_function(self.end); match self.handles { @@ -125,18 +125,18 @@ impl Bezier { /// pub fn rotate(&self, angle: f64) -> Bezier { let rotation_matrix = DMat2::from_angle(angle); - self.apply_transformation(&|point| rotation_matrix.mul_vec2(point)) + self.apply_transformation(|point| rotation_matrix.mul_vec2(point)) } /// Returns a Bezier curve that results from rotating the curve around the provided point by the given angle (in radians). pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Bezier { let rotation_matrix = DMat2::from_angle(angle); - self.apply_transformation(&|point| rotation_matrix.mul_vec2(point - pivot) + pivot) + self.apply_transformation(|point| rotation_matrix.mul_vec2(point - pivot) + pivot) } /// Returns a Bezier curve that results from translating the curve by the given `DVec2`. pub fn translate(&self, translation: DVec2) -> Bezier { - self.apply_transformation(&|point| point + translation) + self.apply_transformation(|point| point + translation) } /// Determine if it is possible to scale the given curve, using the following conditions: @@ -163,7 +163,7 @@ impl Bezier { } /// Add the bezier endpoints if not already present, and combine and sort the dimensional extrema. - fn get_extrema_t_list(&self) -> Vec { + pub(crate) fn get_extrema_t_list(&self) -> Vec { let mut extrema = self.local_extrema().into_iter().flatten().collect::>(); extrema.append(&mut vec![0., 1.]); extrema.dedup(); @@ -274,7 +274,7 @@ impl Bezier { }; let should_flip_direction = (self.start - intersection).normalize().abs_diff_eq(normal_start, MAX_ABSOLUTE_DIFFERENCE); - intermediate.apply_transformation(&|point| { + intermediate.apply_transformation(|point| { let mut direction_unit_vector = (intersection - point).normalize(); if should_flip_direction { direction_unit_vector *= -1.; diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 04b5924274..de6768aecf 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -123,6 +123,20 @@ impl Subpath { let _ = write!(svg, r#""#, curve_start_argument, curve_arguments.join(" ")); } + /// Write the curve argument to the string (the d="..." part) + pub fn subpath_to_svg(&self, svg: &mut String, transform: glam::DAffine2) -> std::fmt::Result { + let start = transform.transform_point2(self[0].anchor); + write!(svg, "{SVG_ARG_MOVE}{},{}", start.x, start.y)?; + for bezier in self.iter() { + bezier.apply_transformation(|pos| transform.transform_point2(pos)).write_curve_argument(svg)?; + svg.push(' '); + } + if self.closed { + svg.push_str(SVG_ARG_CLOSED); + } + Ok(()) + } + /// Appends to the `svg` mutable string with an SVG shape representation of the handle lines. pub fn handle_lines_to_svg(&self, svg: &mut String, attributes: String) { let handle_lines: Vec = self.iter().filter_map(|bezier| bezier.svg_handle_line_argument()).collect(); @@ -178,7 +192,7 @@ impl Subpath { Self::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true) } - /// Constructs an elipse with `corner1` and `corner2` as the two corners of the bounding box. + /// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box. pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self { let size = (corner1 - corner2).abs(); let center = (corner1 + corner2) / 2.; @@ -192,10 +206,10 @@ impl Subpath { let handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5; let manipulator_groups = vec![ - ManipulatorGroup::new(top, Some(top + handle_offset * DVec2::X), Some(top - handle_offset * DVec2::X)), - ManipulatorGroup::new(right, Some(right + handle_offset * DVec2::Y), Some(right - handle_offset * DVec2::Y)), - ManipulatorGroup::new(bottom, Some(bottom - handle_offset * DVec2::X), Some(bottom + handle_offset * DVec2::X)), - ManipulatorGroup::new(left, Some(left - handle_offset * DVec2::Y), Some(left + handle_offset * DVec2::Y)), + ManipulatorGroup::new(top, Some(top - handle_offset * DVec2::X), Some(top + handle_offset * DVec2::X)), + ManipulatorGroup::new(right, Some(right - handle_offset * DVec2::Y), Some(right + handle_offset * DVec2::Y)), + ManipulatorGroup::new(bottom, Some(bottom + handle_offset * DVec2::X), Some(bottom - handle_offset * DVec2::X)), + ManipulatorGroup::new(left, Some(left + handle_offset * DVec2::Y), Some(left - handle_offset * DVec2::Y)), ]; Self::new(manipulator_groups, true) } diff --git a/libraries/bezier-rs/src/subpath/manipulators.rs b/libraries/bezier-rs/src/subpath/manipulators.rs index bb07fbd77b..6bb2fd9ddf 100644 --- a/libraries/bezier-rs/src/subpath/manipulators.rs +++ b/libraries/bezier-rs/src/subpath/manipulators.rs @@ -9,6 +9,36 @@ impl Subpath { self.closed } + /// Set if the subpath is closed. + pub fn set_closed(&mut self, new_closed: bool) { + self.closed = new_closed; + } + + /// Access a [ManipulatorGroup] from a [ManipulatorGroupId]. + pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup> { + self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id) + } + + /// Access a mutable [ManipulatorGroup] from a [ManipulatorGroupId]. + pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup> { + self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id) + } + + /// Access the index of a [ManipulatorGroup] from a [ManipulatorGroupId]. + pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option { + self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id) + } + + /// Insert a manipulator group at an index + pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup) { + self.manipulator_groups.insert(index, group) + } + + /// Remove a manipulator group at an index + pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup { + self.manipulator_groups.remove(index) + } + /// Inserts a `ManipulatorGroup` at a certain point along the subpath based on the parametric `t`-value provided. /// Expects `t` to be within the inclusive range `[0, 1]`. pub fn insert(&mut self, t: SubpathTValue) { diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index 42a2ee3672..7b93fd0b1b 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -103,6 +103,13 @@ impl Subpath { self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) } + /// Return the min and max corners that represent the bounding box of the subpath, after a given affine transform. + pub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> { + self.iter() + .map(|bezier| bezier.apply_transformation(&|v| transform.transform_point2(v)).bounding_box()) + .reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) + } + /// Returns list of `t`-values representing the inflection points of the subpath. /// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`. /// @@ -123,6 +130,11 @@ impl Subpath { // TODO: Consider the shared point between adjacent beziers. inflection_t_values } + + /// Does a path contain a point? Based on the non zero winding + pub fn contains_point(&self, target_point: DVec2) -> bool { + self.iter().map(|bezier| bezier.winding(target_point)).sum::() != 0 + } } #[cfg(test)] diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 6602f5d77d..ab0c3bf891 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -7,26 +7,31 @@ use crate::vector::VectorData; use crate::Node; #[derive(Debug, Clone, Copy)] -pub struct TransformNode { +pub struct TransformNode { pub(crate) translate: Translation, pub(crate) rotate: Rotation, pub(crate) scale: Scale, pub(crate) shear: Shear, + pub(crate) pivot: Pivot, } #[node_macro::node_fn(TransformNode)] -pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2) -> VectorData { - let transform = generate_transform(shear, &vector_data.transform, scale, rotate, translate); - vector_data.transform = transform * vector_data.transform; +pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> VectorData { + let pivot = DAffine2::from_translation(vector_data.local_pivot(pivot)); + + let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse(); + vector_data.transform = modification * vector_data.transform; + vector_data } -impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input> Node<'input, ImageFrame> for TransformNode +impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input, Pivot: 'input> Node<'input, ImageFrame> for TransformNode where Translation: for<'any_input> Node<'any_input, (), Output = DVec2>, Rotation: for<'any_input> Node<'any_input, (), Output = f64>, Scale: for<'any_input> Node<'any_input, (), Output = DVec2>, Shear: for<'any_input> Node<'any_input, (), Output = DVec2>, + Pivot: for<'any_input> Node<'any_input, (), Output = DVec2>, { type Output = ImageFrame; #[inline] @@ -48,6 +53,5 @@ fn generate_transform(shear: DVec2, transform: &DAffine2, scale: DVec2, rotate: let pivot = transform.transform_point2(DVec2::splat(0.5)); let translate_to_center = DAffine2::from_translation(-pivot); - let transformation = translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center; - transformation + translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center } diff --git a/node-graph/gcore/src/uuid.rs b/node-graph/gcore/src/uuid.rs index 79bd74283f..81119b2534 100644 --- a/node-graph/gcore/src/uuid.rs +++ b/node-graph/gcore/src/uuid.rs @@ -1,3 +1,4 @@ +use dyn_any::{DynAny, StaticType}; use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Serialize, Deserialize, specta::Type)] @@ -70,7 +71,7 @@ mod uuid_generation { pub use uuid_generation::*; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ManipulatorGroupId(u64); diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 3ba0aff2fc..9f99c217c7 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -17,41 +17,43 @@ pub struct UnitSquareGenerator; #[node_macro::node_fn(UnitSquareGenerator)] fn unit_square(_input: ()) -> VectorData { - super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)) + super::VectorData::from_subpaths(vec![Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)]) } // TODO: I removed the Arc requirement we shouuld think about when it makes sense to use its // vs making a generic value node #[derive(Debug, Clone)] -pub struct PathGenerator; +pub struct PathGenerator { + mirror: Mirror, +} #[node_macro::node_fn(PathGenerator)] -fn generate_path(path_data: Subpath) -> super::VectorData { - super::VectorData::from_subpath(path_data) +fn generate_path(path_data: Vec>, mirror: Vec) -> super::VectorData { + let mut vector_data = super::VectorData::from_subpaths(path_data); + vector_data.mirror_angle = mirror; + vector_data } -use crate::raster::Image; +// #[derive(Debug, Clone, Copy)] +// pub struct BlitSubpath

{ +// path_data: P, +// } -#[derive(Debug, Clone, Copy)] -pub struct BlitSubpath

{ - path_data: P, -} +// #[node_macro::node_fn(BlitSubpath)] +// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image { +// // TODO: Get forma to compile +// use forma::prelude::*; +// let composition = Composition::new(); +// let mut renderer = cpu::Renderer::new(); +// let mut path_builder = PathBuilder::new(); +// for path_segment in path_data.bezier_iter() { +// let points = path_segment.internal.get_points().collect::>(); +// match points.len() { +// 2 => path_builder.line_to(points[1].into()), +// 3 => path_builder.quad_to(points[1].into(), points[2].into()), +// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()), +// } +// } -#[node_macro::node_fn(BlitSubpath)] -fn bilt_subpath(base_image: Image, path_data: VectorData) -> Image { - // TODO: Get forma to compile - /*use forma::prelude::*; - let composition = Composition::new(); - let mut renderer = cpu::Renderer::new(); - let mut path_builder = PathBuilder::new(); - for path_segment in path_data.bezier_iter() { - let points = path_segment.internal.get_points().collect::>(); - match points.len() { - 2 => path_builder.line_to(points[1].into()), - 3 => path_builder.quad_to(points[1].into(), points[2].into()), - 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()), - } - }*/ - - base_image -} +// base_image +// } diff --git a/node-graph/gcore/src/vector/mod.rs b/node-graph/gcore/src/vector/mod.rs index eb8a58bb02..da93c9f598 100644 --- a/node-graph/gcore/src/vector/mod.rs +++ b/node-graph/gcore/src/vector/mod.rs @@ -10,7 +10,7 @@ pub mod subpath; pub use subpath::Subpath; mod vector_data; -pub use vector_data::VectorData; +pub use vector_data::*; mod id_vec; pub use id_vec::IdBackedVec; diff --git a/node-graph/gcore/src/vector/subpath.rs b/node-graph/gcore/src/vector/subpath.rs index e61f88d681..16618b67da 100644 --- a/node-graph/gcore/src/vector/subpath.rs +++ b/node-graph/gcore/src/vector/subpath.rs @@ -1,3 +1,5 @@ +use crate::uuid::ManipulatorGroupId; + use super::consts::ManipulatorType; use super::id_vec::IdBackedVec; use super::manipulator_group::ManipulatorGroup; @@ -45,6 +47,21 @@ impl Subpath { shape.path_elements(0.1).into() } + /// Convert to the legacy Subpath from the `bezier_rs::Subpath`. + pub fn from_bezier_crate(value: &[bezier_rs::Subpath]) -> Self { + let mut groups = IdBackedVec::new(); + for subpath in value { + for group in subpath.manipulator_groups() { + groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle)); + } + if subpath.closed() { + let group = subpath.manipulator_groups()[0]; + groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle)); + } + } + Self(groups) + } + // ** PRIMITIVE CONSTRUCTION ** /// constructs a rectangle with `p1` as the lower left and `p2` as the top right diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 69e946793c..af1785c1c2 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -1,8 +1,9 @@ use super::style::{PathStyle, Stroke}; use crate::{uuid::ManipulatorGroupId, Color}; +use bezier_rs::ManipulatorGroup; use dyn_any::{DynAny, StaticType}; -use glam::DAffine2; +use glam::{DAffine2, DVec2}; /// [VectorData] is passed between nodes. /// It contains a list of subpaths (that may be open or closed), a transform and some style information. @@ -12,22 +13,146 @@ pub struct VectorData { pub subpaths: Vec>, pub transform: DAffine2, pub style: PathStyle, + pub mirror_angle: Vec, } impl VectorData { + /// An empty subpath with no data, an identity transform and a black fill. pub const fn empty() -> Self { Self { subpaths: Vec::new(), transform: DAffine2::IDENTITY, - style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::Solid(Color::BLACK)), + style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::None), + mirror_angle: Vec::new(), } } + /// Iterator over the manipulator groups of the subpaths + pub fn manipulator_groups(&self) -> impl Iterator> + DoubleEndedIterator { + self.subpaths.iter().flat_map(|subpath| subpath.manipulator_groups()) + } + + pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup> { + self.subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id)) + } + + /// Construct some new vector data from a single subpath with an identy transform and black fill. pub fn from_subpath(subpath: bezier_rs::Subpath) -> Self { - super::VectorData { - subpaths: vec![subpath], - transform: DAffine2::IDENTITY, - style: PathStyle::default(), + Self::from_subpaths(vec![subpath]) + } + + /// Construct some new vector data from subpaths with an identy transform and black fill. + pub fn from_subpaths(subpaths: Vec>) -> Self { + super::VectorData { subpaths, ..Self::empty() } + } + + /// Compute the bounding boxes of the subpaths without any transform + pub fn bounding_box(&self) -> Option<[DVec2; 2]> { + self.bounding_box_with_transform(DAffine2::IDENTITY) + } + + /// Compute the bounding boxes of the subpaths with the specified transform + pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { + self.subpaths + .iter() + .filter_map(|subpath| subpath.bounding_box_with_transform(transform)) + .reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])]) + } + + /// Calculate the corners of the bounding box but with a nonzero size. + /// + /// If the layer bounds are `0` in either axis then they are changed to be `1`. + pub fn nonzero_bounding_box(&self) -> [DVec2; 2] { + let [bounds_min, mut bounds_max] = self.bounding_box().unwrap_or_default(); + + let bounds_size = bounds_max - bounds_min; + if bounds_size.x < 1e-10 { + bounds_max.x = bounds_min.x + 1.; } + if bounds_size.y < 1e-10 { + bounds_max.y = bounds_min.y + 1.; + } + + [bounds_min, bounds_max] + } + + /// Compute the pivot of the layer in layerspace (the coordinates of the subpaths) + pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 { + let [bounds_min, bounds_max] = self.nonzero_bounding_box(); + let bounds_size = bounds_max - bounds_min; + bounds_min + bounds_size * normalised_pivot + } + + /// Compute the pivot in local space with the current transform applied + pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 { + self.transform.transform_point2(self.layerspace_pivot(normalised_pivot)) + } +} + +impl Default for VectorData { + fn default() -> Self { + Self::empty() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ManipulatorPointId { + pub group: ManipulatorGroupId, + pub manipulator_type: SelectedType, +} +impl ManipulatorPointId { + pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self { + Self { group, manipulator_type } + } +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SelectedType { + Anchor = 1 << 0, + InHandle = 1 << 1, + OutHandle = 1 << 2, +} +impl SelectedType { + /// Get the location of the [SelectedType] in the [ManipulatorGroup] + pub fn get_position(&self, manipulator_group: &ManipulatorGroup) -> Option { + match self { + Self::Anchor => Some(manipulator_group.anchor), + Self::InHandle => manipulator_group.in_handle, + Self::OutHandle => manipulator_group.out_handle, + } + } + + /// Get the closest [SelectedType] in the [ManipulatorGroup]. + pub fn closest_widget(manipulator_group: &ManipulatorGroup, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) { + let anchor = transform_space.transform_point2(manipulator_group.anchor); + // Skip handles under the anchor + let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2); + let compute_distance = |selected_type: Self| { + selected_type.get_position(manipulator_group).and_then(|position| { + Some((selected_type, transform_space.transform_point2(position))) + .filter(not_under_anchor) + .map(|(selected_type, pos)| (selected_type, pos.distance_squared(target))) + }) + }; + [Self::Anchor, Self::InHandle, Self::OutHandle] + .into_iter() + .filter_map(compute_distance) + .min_by(|a, b| a.1.total_cmp(&b.1)) + .unwrap_or((Self::Anchor, manipulator_group.anchor.distance_squared(target))) + } + + /// Opposite handle + pub fn opposite(&self) -> Self { + match self { + SelectedType::Anchor => SelectedType::Anchor, + SelectedType::InHandle => SelectedType::OutHandle, + SelectedType::OutHandle => SelectedType::InHandle, + } + } + + /// Check if handle + pub fn is_handle(self) -> bool { + self != SelectedType::Anchor } } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 774f665d95..2ce4c8f8b5 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -18,7 +18,7 @@ pub struct SetFillNode, gradient_type: GradientType, start: DVec2, end: DVec2, @@ -26,8 +26,7 @@ fn set_vector_data_fill( positions: Vec<(f64, Option)>, ) -> VectorData { vector_data.style.set_fill(match fill_type { - FillType::None => Fill::None, - FillType::Solid => Fill::Solid(solid_color), + FillType::None | FillType::Solid => solid_color.map_or(Fill::None, |solid_color| Fill::Solid(solid_color)), FillType::Gradient => Fill::Gradient(Gradient { start, end, diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 2a22c6f093..7b57f0dc36 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -123,27 +123,25 @@ impl DocumentNode { } /// Represents the possible inputs to a node. -/// # ShortCircuting +/// +/// # Short circuting +/// /// In Graphite nodes are functions and by default, these are composed into a single function /// by inserting Compose nodes. /// -/// -/// -/// +/// ```text /// ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ /// │ │◄──────────────┤ │◄───────────────┤ │ /// │ A │ │ B │ │ C │ /// │ ├──────────────►│ ├───────────────►│ │ /// └─────────────────┘ └──────────────────┘ └──────────────────┘ +/// ``` /// -/// -/// -/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`) +/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`). /// But sometimes we might want to have a little more control over the order of execution. /// This is why we allow nodes to opt out of the input forwarding by consuming the input directly. /// -/// -/// +/// ```text /// ┌─────────────────────┐ ┌─────────────┐ /// │ │◄───────────────┤ │ /// │ Cache Node │ │ C │ @@ -153,20 +151,26 @@ impl DocumentNode { /// │ A │ │ * Cached Node │ /// │ ├──────────────►│ │ /// └──────────────────┘ └─────────────────────┘ +/// ``` /// -/// -/// -/// -/// In this case the Cache node actually consumes it's input and then manually forwards it to it's parameter -/// Node. This is necessary because the Cache Node needs to short-circut the actual node evaluation +/// In this case the Cache node actually consumes its input and then manually forwards it to its parameter Node. +/// This is necessary because the Cache Node needs to short-circut the actual node evaluation. #[derive(Debug, Clone, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum NodeInput { - Node { node_id: NodeId, output_index: usize, lambda: bool }, - Value { tagged_value: crate::document::value::TaggedValue, exposed: bool }, + Node { + node_id: NodeId, + output_index: usize, + lambda: bool, + }, + Value { + tagged_value: crate::document::value::TaggedValue, + exposed: bool, + }, Network(Type), - // A short circuting input represents an input that is not resolved through function composition but - // actually consuming the provided input instead of passing it to its predecessor + /// A short circuting input represents an input that is not resolved through function composition + /// but actually consuming the provided input instead of passing it to its predecessor. + /// See [NodeInput] docs for more explanation. ShortCircut(Type), } @@ -293,7 +297,7 @@ impl NodeNetwork { let mut duplicating_nodes = HashMap::new(); // Find the nodes where the inputs require duplicating for node in &mut self.nodes.values_mut() { - // Recursivly duplicate children + // Recursively duplicate children if let DocumentNodeImplementation::Network(network) = &mut node.implementation { network.duplicate_outputs(gen_id); } @@ -420,7 +424,6 @@ impl NodeNetwork { network_input.populate_first_network_input(node_id, output_index, *offset, lambda); } NodeInput::Value { tagged_value, exposed } => { - // Skip formatting very large values for seconds in performance speedup let name = "Value".to_string(); let new_id = map_ids(id, gen_id()); let value_node = DocumentNode { @@ -588,6 +591,38 @@ impl NodeNetwork { pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option { self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id)) } + + /// A iterator of all nodes connected by primary inputs. + /// + /// Used for the properties panel and tools. + pub fn primary_flow(&self) -> impl Iterator { + struct FlowIter<'a> { + stack: Vec, + network: &'a NodeNetwork, + } + impl<'a> Iterator for FlowIter<'a> { + type Item = (&'a DocumentNode, NodeId); + fn next(&mut self) -> Option { + loop { + let node_id = self.stack.pop()?; + if let Some(document_node) = self.network.nodes.get(&node_id) { + self.stack.extend( + document_node + .inputs + .iter() + .take(1) // Only show the primary input + .filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }), + ); + return Some((document_node, node_id)); + }; + } + } + } + FlowIter { + stack: self.outputs.iter().map(|output| output.node_id).collect(), + network: &self, + } + } } #[cfg(test)] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index fb14ed8236..d793a6df24 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -27,7 +27,7 @@ pub enum TaggedValue { RcImage(Option>), ImageFrame(graphene_core::raster::ImageFrame), Color(graphene_core::raster::color::Color), - Subpath(bezier_rs::Subpath), + Subpaths(Vec>), RcSubpath(Arc>), BlendMode(BlendMode), LuminanceCalculation(LuminanceCalculation), @@ -45,141 +45,65 @@ pub enum TaggedValue { GradientType(graphene_core::vector::style::GradientType), GradientPositions(Vec<(f64, Option)>), Quantization(graphene_core::quantization::QuantizationChannels), + OptionalColor(Option), + ManipulatorGroupIds(Vec), } #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for TaggedValue { fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); match self { - Self::None => 0.hash(state), - Self::String(s) => { - 1.hash(state); - s.hash(state) - } - Self::U32(u) => { - 2.hash(state); - u.hash(state) - } - Self::F32(f) => { - 3.hash(state); - f.to_bits().hash(state) - } - Self::F64(f) => { - 4.hash(state); - f.to_bits().hash(state) - } - Self::Bool(b) => { - 5.hash(state); - b.hash(state) - } - Self::DVec2(v) => { - 6.hash(state); - v.to_array().iter().for_each(|x| x.to_bits().hash(state)) - } - Self::OptionalDVec2(None) => 7.hash(state), + Self::None => {} + Self::String(s) => s.hash(state), + Self::U32(u) => u.hash(state), + Self::F32(f) => f.to_bits().hash(state), + Self::F64(f) => f.to_bits().hash(state), + Self::Bool(b) => b.hash(state), + Self::DVec2(v) => v.to_array().iter().for_each(|x| x.to_bits().hash(state)), + Self::OptionalDVec2(None) => 0.hash(state), Self::OptionalDVec2(Some(v)) => { - 8.hash(state); + 1.hash(state); Self::DVec2(*v).hash(state) } - Self::DAffine2(m) => { - 9.hash(state); - m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) - } - Self::Image(i) => { - 10.hash(state); - i.hash(state) - } - Self::RcImage(i) => { - 11.hash(state); - i.hash(state) - } - Self::Color(c) => { - 12.hash(state); - c.hash(state) - } - Self::Subpath(s) => { - 13.hash(state); - s.hash(state) - } - Self::RcSubpath(s) => { - 14.hash(state); - s.hash(state) - } - Self::BlendMode(b) => { - 15.hash(state); - b.hash(state) - } - Self::LuminanceCalculation(l) => { - 16.hash(state); - l.hash(state) - } - Self::ImaginateSamplingMethod(m) => { - 17.hash(state); - m.hash(state) - } - Self::ImaginateMaskStartingFill(f) => { - 18.hash(state); - f.hash(state) - } - Self::ImaginateStatus(s) => { - 19.hash(state); - s.hash(state) - } - Self::LayerPath(p) => { - 20.hash(state); - p.hash(state) - } + Self::DAffine2(m) => m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)), + Self::Image(i) => i.hash(state), + Self::RcImage(i) => i.hash(state), + Self::Color(c) => c.hash(state), + Self::Subpaths(s) => s.iter().for_each(|subpath| subpath.hash(state)), + Self::RcSubpath(s) => s.hash(state), + Self::BlendMode(b) => b.hash(state), + Self::LuminanceCalculation(l) => l.hash(state), + Self::ImaginateSamplingMethod(m) => m.hash(state), + Self::ImaginateMaskStartingFill(f) => f.hash(state), + Self::ImaginateStatus(s) => s.hash(state), + Self::LayerPath(p) => p.hash(state), Self::ImageFrame(i) => { - 21.hash(state); i.image.hash(state); i.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) } Self::VectorData(vector_data) => { - 22.hash(state); vector_data.subpaths.hash(state); vector_data.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); vector_data.style.hash(state); } - Self::Fill(fill) => { - 23.hash(state); - fill.hash(state); - } - Self::Stroke(stroke) => { - 24.hash(state); - stroke.hash(state); - } - Self::VecF32(vec_f32) => { - 25.hash(state); - vec_f32.iter().for_each(|val| val.to_bits().hash(state)); - } - Self::LineCap(line_cap) => { - 26.hash(state); - line_cap.hash(state); - } - Self::LineJoin(line_join) => { - 27.hash(state); - line_join.hash(state); - } - Self::FillType(fill_type) => { - 28.hash(state); - fill_type.hash(state); - } - Self::GradientType(gradient_type) => { - 29.hash(state); - gradient_type.hash(state); - } + Self::Fill(fill) => fill.hash(state), + Self::Stroke(stroke) => stroke.hash(state), + Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)), + Self::LineCap(line_cap) => line_cap.hash(state), + Self::LineJoin(line_join) => line_join.hash(state), + Self::FillType(fill_type) => fill_type.hash(state), + Self::GradientType(gradient_type) => gradient_type.hash(state), Self::GradientPositions(gradient_positions) => { - 30.hash(state); gradient_positions.len().hash(state); for (position, color) in gradient_positions { position.to_bits().hash(state); color.hash(state); } } - Self::Quantization(quantized_image) => { - 31.hash(state); - quantized_image.hash(state); - } + Self::Quantization(quantized_image) => quantized_image.hash(state), + Self::OptionalColor(color) => color.hash(state), + Self::ManipulatorGroupIds(mirror) => mirror.hash(state), } } } @@ -201,7 +125,7 @@ impl<'a> TaggedValue { TaggedValue::RcImage(x) => Box::new(x), TaggedValue::ImageFrame(x) => Box::new(x), TaggedValue::Color(x) => Box::new(x), - TaggedValue::Subpath(x) => Box::new(x), + TaggedValue::Subpaths(x) => Box::new(x), TaggedValue::RcSubpath(x) => Box::new(x), TaggedValue::BlendMode(x) => Box::new(x), TaggedValue::LuminanceCalculation(x) => Box::new(x), @@ -219,6 +143,8 @@ impl<'a> TaggedValue { TaggedValue::GradientType(x) => Box::new(x), TaggedValue::GradientPositions(x) => Box::new(x), TaggedValue::Quantization(x) => Box::new(x), + TaggedValue::OptionalColor(x) => Box::new(x), + TaggedValue::ManipulatorGroupIds(x) => Box::new(x), } } @@ -238,7 +164,7 @@ impl<'a> TaggedValue { TaggedValue::RcImage(_) => concrete!(Option>), TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame), TaggedValue::Color(_) => concrete!(graphene_core::raster::Color), - TaggedValue::Subpath(_) => concrete!(bezier_rs::Subpath), + TaggedValue::Subpaths(_) => concrete!(Vec>), TaggedValue::RcSubpath(_) => concrete!(Arc>), TaggedValue::BlendMode(_) => concrete!(BlendMode), TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod), @@ -257,6 +183,8 @@ impl<'a> TaggedValue { TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType), TaggedValue::GradientPositions(_) => concrete!(Vec<(f64, Option)>), TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels), + TaggedValue::OptionalColor(_) => concrete!(Option), + TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec), } } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 3cb048d92f..948e97dabe 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -609,12 +609,12 @@ mod test { assert_eq!( ids, vec![ - 7332206428857154453, - 946497269036214321, - 3038115864048241698, - 1932610308557160863, - 2105748431407297710, - 8596220090685862327 + 10739226043134366700, + 17332796976541881019, + 7897288931440576543, + 7388412494950743023, + 359700384277940942, + 12822947441562012352 ] ); } diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 0f7558b068..e9ed6a86fb 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -30,7 +30,7 @@ where fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output { { let node_name = core::any::type_name::(); - let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}")); + let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}")); Box::new(self.node.eval(*input)) } } @@ -52,7 +52,7 @@ where fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output { { let node_name = core::any::type_name::(); - let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}")); + let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyInRefNode Input, {e} in:\n{node_name}")); Box::new(self.node.eval(*input)) } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index afe0da93b9..2050ac88dc 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -197,6 +197,16 @@ fn node_registry() -> HashMap"), + |args| { + let input: DowncastBothNode<(), VectorData> = DowncastBothNode::new(args[0]); + let node = graphene_std::memo::EndLetNode::new(input); + let any: DynAnyInRefNode = graphene_std::any::DynAnyInRefNode::new(node); + any.into_type_erased() + }, + NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(VectorData))]), + ), ( NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"), |args| { @@ -336,15 +346,15 @@ fn node_registry() -> HashMap, params: [QuantizationChannels]), raster_node!(graphene_core::quantization::DeQuantizeNode<_>, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), - register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2]), - register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2]), - register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, graphene_core::Color, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option)>]), + register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]), + register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]), + register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, Option, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option)>]), register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::Color, f64, Vec, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]), register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []), register_node!( - graphene_core::vector::generator_nodes::PathGenerator, - input: graphene_core::vector::bezier_rs::Subpath, - params: [] + graphene_core::vector::generator_nodes::PathGenerator<_>, + input: Vec>, + params: [Vec] ), ]; let mut map: HashMap> = HashMap::new();