From 4cd72edb64c0395f3782cf649f00cc757909cdec Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Sun, 30 Jul 2023 20:16:08 +0100 Subject: [PATCH] Initial work migrating vector layers to document graph * Fix pen tool (except overlays) * Thumbnail of only the layer and not the composite * Fix occasional transform breakages * Constrain size of thumbnail * Insert new layers at the top * Broken layer tree * Fix crash when drawing * Reduce calls to send graph * Reduce calls to updating properties * Store cached transforms upon the document * Fix missing node UI updates * Fix fill tool and clean up imports and indentation * Error on overide existing layer * Fix pen tool (partially) * Fix some lints --- document-legacy/src/document.rs | 4 + document-legacy/src/document_metadata.rs | 407 ++++++++ document-legacy/src/lib.rs | 1 + editor/src/dispatcher.rs | 4 + .../src/messages/frontend/frontend_message.rs | 4 + .../portfolio/document/document_message.rs | 3 + .../document/document_message_handler.rs | 28 +- .../navigation/navigation_message_handler.rs | 18 +- .../node_graph/graph_operation_message.rs | 7 +- .../graph_operation_message_handler.rs | 160 ++-- .../node_graph/node_graph_message_handler.rs | 44 +- .../document_node_types.rs | 39 +- .../portfolio/portfolio_message_handler.rs | 11 +- .../graph_modification_utils.rs | 71 +- .../common_functionality/overlay_renderer.rs | 170 ++-- .../tool/common_functionality/resize.rs | 4 +- .../tool/common_functionality/shape_editor.rs | 227 +++-- .../tool/common_functionality/snapping.rs | 8 +- .../tool/tool_messages/artboard_tool.rs | 576 ++++++------ .../messages/tool/tool_messages/brush_tool.rs | 228 +++-- .../tool/tool_messages/ellipse_tool.rs | 193 ++-- .../tool/tool_messages/eyedropper_tool.rs | 97 +- .../messages/tool/tool_messages/fill_tool.rs | 108 +-- .../messages/tool/tool_messages/frame_tool.rs | 137 ++- .../tool/tool_messages/freehand_tool.rs | 165 ++-- .../tool/tool_messages/gradient_tool.rs | 475 +++++----- .../tool/tool_messages/imaginate_tool.rs | 187 ++-- .../messages/tool/tool_messages/line_tool.rs | 161 ++-- editor/src/messages/tool/tool_messages/mod.rs | 12 + .../tool/tool_messages/navigate_tool.rs | 104 +- .../messages/tool/tool_messages/path_tool.rs | 597 ++++++------ .../messages/tool/tool_messages/pen_tool.rs | 349 ++++--- .../tool/tool_messages/polygon_tool.rs | 195 ++-- .../tool/tool_messages/rectangle_tool.rs | 172 ++-- .../tool/tool_messages/select_tool.rs | 888 +++++++++--------- .../tool/tool_messages/spline_tool.rs | 211 ++--- .../messages/tool/tool_messages/text_tool.rs | 216 ++--- .../transform_layer_message_handler.rs | 6 +- editor/src/node_graph_executor.rs | 101 +- frontend/src/components/views/Graph.svelte | 10 +- frontend/src/state-providers/node-graph.ts | 15 + frontend/src/wasm-communication/messages.ts | 9 +- node-graph/gcore/src/graphic_element.rs | 7 + .../gcore/src/graphic_element/renderer.rs | 83 +- .../src/graphic_element/renderer/quad.rs | 57 +- node-graph/gcore/src/transform.rs | 47 + node-graph/graph-craft/src/document.rs | 4 +- node-graph/gstd/src/gpu_nodes.rs | 6 +- .../interpreted-executor/src/node_registry.rs | 11 +- proc-macros/src/helpers.rs | 1 - 50 files changed, 3585 insertions(+), 3053 deletions(-) create mode 100644 document-legacy/src/document_metadata.rs diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 3b175f2e20..a83ff031d1 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -1,3 +1,4 @@ +use crate::document_metadata::DocumentMetadata; use crate::intersection::Quad; use crate::layers::folder_layer::FolderLayer; use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant}; @@ -28,6 +29,8 @@ pub struct Document { pub state_identifier: DefaultHasher, #[serde(default)] pub document_network: graph_craft::document::NodeNetwork, + #[serde(skip)] + pub metadata: DocumentMetadata, #[serde(default)] pub commit_hash: String, } @@ -56,6 +59,7 @@ impl Default for Document { network.push_node(node, false); network }, + metadata: Default::default(), commit_hash: String::new(), } } diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs new file mode 100644 index 0000000000..dbb1d33915 --- /dev/null +++ b/document-legacy/src/document_metadata.rs @@ -0,0 +1,407 @@ +use glam::{DAffine2, DVec2}; +use graphene_core::renderer::ClickTarget; +use std::collections::HashMap; +use std::num::NonZeroU64; + +use graph_craft::document::{NodeId, NodeNetwork}; + +use graphene_core::renderer::Quad; + +#[derive(Debug, Clone)] +pub struct DocumentMetadata { + transforms: HashMap, + structure: HashMap, + click_targets: HashMap>, + /// Transform from document space to viewport space. + pub document_to_viewport: DAffine2, +} + +impl Default for DocumentMetadata { + fn default() -> Self { + Self { + transforms: HashMap::new(), + click_targets: HashMap::new(), + structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]), + document_to_viewport: DAffine2::IDENTITY, + } + } +} + +impl DocumentMetadata { + /// Get the root layer from the document + pub const fn root(&self) -> LayerNodeIdentifier { + LayerNodeIdentifier::ROOT + } + + pub fn all_layers(&self) -> DecendantsIter<'_> { + self.root().decendants(self) + } + + pub fn selected_layers(&self) -> impl Iterator + '_ { + self.all_layers() + } + + /// Access the [`NodeRelations`] of a layer + fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> { + self.structure.get(&node_identifier) + } + + /// Mutably access the [`NodeRelations`] of a layer + fn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations { + self.structure.entry(node_identifier).or_default() + } + + /// Update the cached transforms of the layers + pub fn update_transforms(&mut self, new_transforms: HashMap) { + self.transforms = new_transforms; + } + + /// Update the cached click targets of the layers + pub fn update_click_targets(&mut self, new_click_targets: HashMap>) { + self.click_targets = new_click_targets; + } + + /// Access the cached transformation from document space to layer space + pub fn transform_from_document(&self, layer: LayerNodeIdentifier) -> DAffine2 { + self.transforms.get(&layer).copied().unwrap_or_else(|| { + warn!("Tried to access transform of bad layer"); + DAffine2::IDENTITY + }) + } + + pub fn transform_from_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { + self.document_to_viewport * self.transform_from_document(layer) + } + + /// Runs an intersection test with all layers and a document space quad + pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<(LayerNodeIdentifier, &Vec)> { + let document_quad = self.document_to_viewport.inverse() * viewport_quad; + self.root() + .decendants(self) + .filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets))) + .find(|(layer, target)| target.iter().any(|target| target.intersect_rectangle(document_quad, self.transform_from_document(*layer)))) + } + + /// Find the layer that has been clicked on from a document space location + pub fn click(&self, viewport_location: DVec2) -> Option<(LayerNodeIdentifier, &Vec)> { + let point = self.document_to_viewport.inverse().transform_point2(viewport_location); + self.root() + .decendants(self) + .filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets))) + .find(|(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer)))) + } +} + +/// Id of a layer node +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LayerNodeIdentifier(NonZeroU64); + +impl LayerNodeIdentifier { + const ROOT: Self = LayerNodeIdentifier::new_unchecked(0); + + /// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node + pub const fn new_unchecked(node_id: NodeId) -> Self { + // Safety: will always be >=1 + Self(unsafe { NonZeroU64::new_unchecked(node_id + 1) }) + } + + /// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node + pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self { + debug_assert!( + is_layer_node(node_id, network), + "Layer identifer constructed from non layer node {node_id}: {:#?}", + network.nodes.get(&node_id) + ); + Self::new_unchecked(node_id) + } + + pub fn from_path(path: &[u64], network: &NodeNetwork) -> Self { + Self::new(*path.last().unwrap(), network) + } + + /// Access the node id of this layer + pub fn to_node(self) -> NodeId { + u64::from(self.0) - 1 + } + + /// Convert layer to layer path + pub fn to_path(self) -> Vec { + vec![self.to_node()] + } + + /// Access the parent layer if possible + pub fn parent(self, document_metadata: &DocumentMetadata) -> Option { + document_metadata.get_relations(self).and_then(|relations| relations.parent) + } + + /// Access the previous sibling of this layer (up the layer tree) + pub fn previous_sibling(self, document_metadata: &DocumentMetadata) -> Option { + document_metadata.get_relations(self).and_then(|relations| relations.previous_sibling) + } + + /// Access the next sibling of this layer (down the layer tree) + pub fn next_sibling(self, document_metadata: &DocumentMetadata) -> Option { + document_metadata.get_relations(self).and_then(|relations| relations.next_sibling) + } + + /// Access the first child of this layer (top most in layer tree) + pub fn first_child(self, document_metadata: &DocumentMetadata) -> Option { + document_metadata.get_relations(self).and_then(|relations| relations.first_child) + } + + /// Access the last child of this layer (bottom most in layer tree) + pub fn last_child(self, document_metadata: &DocumentMetadata) -> Option { + document_metadata.get_relations(self).and_then(|relations| relations.last_child) + } + + /// Does the layer have children? + pub fn has_children(self, document_metadata: &DocumentMetadata) -> bool { + self.first_child(document_metadata).is_some() + } + + /// Iterator over all direct children (excluding self and recursive children) + pub fn children(self, document_metadata: &DocumentMetadata) -> AxisIter { + AxisIter { + layer_node: self.first_child(document_metadata), + next_node: Self::next_sibling, + document_metadata, + } + } + + /// All ancestors of this layer, including self, going to the document root + pub fn ancestors(self, document_metadata: &DocumentMetadata) -> AxisIter { + AxisIter { + layer_node: Some(self), + next_node: Self::parent, + document_metadata, + } + } + + /// Iterator through all the last children, starting from self + pub fn last_children(self, document_metadata: &DocumentMetadata) -> AxisIter { + AxisIter { + layer_node: Some(self), + next_node: Self::last_child, + document_metadata, + } + } + + /// Iterator through all decendants, including recursive children (not including self) + pub fn decendants(self, document_metadata: &DocumentMetadata) -> DecendantsIter { + DecendantsIter { + front: self.first_child(document_metadata), + back: self.last_child(document_metadata), + document_metadata, + } + } + + /// Add a child towards the top of the layer tree + pub fn push_front_child(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) { + assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer"); + let parent = document_metadata.get_structure_mut(self); + let old_first_child = parent.first_child.replace(new); + parent.last_child.get_or_insert(new); + if let Some(old_first_child) = old_first_child { + document_metadata.get_structure_mut(old_first_child).previous_sibling = Some(new); + } + document_metadata.get_structure_mut(new).next_sibling = old_first_child; + document_metadata.get_structure_mut(new).parent = Some(self); + } + + /// Add a child towards the bottom of the layer tree + pub fn push_child(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) { + assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer"); + let parent = document_metadata.get_structure_mut(self); + let old_last_child = parent.last_child.replace(new); + parent.first_child.get_or_insert(new); + if let Some(old_last_child) = old_last_child { + document_metadata.get_structure_mut(old_last_child).next_sibling = Some(new); + } + document_metadata.get_structure_mut(new).previous_sibling = old_last_child; + document_metadata.get_structure_mut(new).parent = Some(self); + } + + /// Add sibling above in the layer tree + pub fn add_before(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) { + assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer"); + document_metadata.get_structure_mut(new).next_sibling = Some(self); + document_metadata.get_structure_mut(new).parent = self.parent(document_metadata); + let old_previous_sibling = document_metadata.get_structure_mut(self).previous_sibling.replace(new); + if let Some(old_previous_sibling) = old_previous_sibling { + document_metadata.get_structure_mut(old_previous_sibling).next_sibling = Some(new); + document_metadata.get_structure_mut(new).previous_sibling = Some(old_previous_sibling); + } else if let Some(structure) = self + .parent(document_metadata) + .map(|parent| document_metadata.get_structure_mut(parent)) + .filter(|structure| structure.first_child == Some(self)) + { + structure.first_child = Some(new); + } + } + + /// Add sibling below in the layer tree + pub fn add_after(self, document_metadata: &mut DocumentMetadata, new: LayerNodeIdentifier) { + assert!(!document_metadata.structure.contains_key(&new), "Cannot add already existing layer"); + document_metadata.get_structure_mut(new).previous_sibling = Some(self); + document_metadata.get_structure_mut(new).parent = self.parent(document_metadata); + let old_next_sibling = document_metadata.get_structure_mut(self).next_sibling.replace(new); + if let Some(old_next_sibling) = old_next_sibling { + document_metadata.get_structure_mut(old_next_sibling).previous_sibling = Some(new); + document_metadata.get_structure_mut(new).next_sibling = Some(old_next_sibling); + } else if let Some(structure) = self + .parent(document_metadata) + .map(|parent| document_metadata.get_structure_mut(parent)) + .filter(|structure| structure.last_child == Some(self)) + { + structure.last_child = Some(new); + } + } + + /// Delete layer and all children + pub fn delete(self, document_metadata: &mut DocumentMetadata) { + let previous_sibling = self.previous_sibling(document_metadata); + let next_sibling = self.next_sibling(document_metadata); + + if let Some(previous_sibling) = previous_sibling.map(|node| document_metadata.get_structure_mut(node)) { + previous_sibling.next_sibling = next_sibling; + } + + if let Some(next_sibling) = next_sibling.map(|node| document_metadata.get_structure_mut(node)) { + next_sibling.previous_sibling = previous_sibling; + } + let mut parent = self.parent(document_metadata).map(|parent| document_metadata.get_structure_mut(parent)); + if let Some(structure) = parent.as_mut().filter(|structure| structure.first_child == Some(self)) { + structure.first_child = next_sibling; + } + if let Some(structure) = parent.as_mut().filter(|structure| structure.last_child == Some(self)) { + structure.last_child = previous_sibling; + } + + let mut delete = vec![self]; + delete.extend(self.decendants(document_metadata)); + for node in delete { + document_metadata.structure.remove(&node); + } + } +} + +impl From for LayerNodeIdentifier { + fn from(node_id: NodeId) -> Self { + Self::new_unchecked(node_id) + } +} + +impl From for NodeId { + fn from(identifer: LayerNodeIdentifier) -> Self { + identifer.to_node() + } +} + +/// Iterator over specified axis. +#[derive(Clone)] +pub struct AxisIter<'a> { + layer_node: Option, + next_node: fn(LayerNodeIdentifier, &DocumentMetadata) -> Option, + document_metadata: &'a DocumentMetadata, +} + +impl<'a> Iterator for AxisIter<'a> { + type Item = LayerNodeIdentifier; + + fn next(&mut self) -> Option { + let layer_node = self.layer_node.take(); + self.layer_node = layer_node.and_then(|node| (self.next_node)(node, self.document_metadata)); + layer_node + } +} + +#[derive(Clone)] +pub struct DecendantsIter<'a> { + front: Option, + back: Option, + document_metadata: &'a DocumentMetadata, +} + +impl<'a> Iterator for DecendantsIter<'a> { + type Item = LayerNodeIdentifier; + + fn next(&mut self) -> Option { + if self.front == self.back { + self.back = None; + self.front.take() + } else { + let layer_node = self.front.take(); + if let Some(layer_node) = layer_node { + self.front = layer_node + .first_child(self.document_metadata) + .or_else(|| layer_node.ancestors(self.document_metadata).find_map(|ancestor| ancestor.next_sibling(self.document_metadata))); + } + layer_node + } + } +} +impl<'a> DoubleEndedIterator for DecendantsIter<'a> { + fn next_back(&mut self) -> Option { + if self.front == self.back { + self.front = None; + self.back.take() + } else { + let layer_node = self.back.take(); + if let Some(layer_node) = layer_node { + self.back = layer_node + .previous_sibling(self.document_metadata) + .and_then(|sibling| sibling.last_children(self.document_metadata).last()) + .or_else(|| layer_node.parent(self.document_metadata)); + } + + layer_node + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct NodeRelations { + parent: Option, + previous_sibling: Option, + next_sibling: Option, + first_child: Option, + last_child: Option, +} + +fn is_layer_node(node: NodeId, network: &NodeNetwork) -> bool { + node == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node).is_some_and(|node| node.name == "Layer") +} + +#[test] +fn test_tree() { + let mut document_metadata = DocumentMetadata::default(); + let root = document_metadata.root(); + let document_metadata = &mut document_metadata; + root.push_child(document_metadata, LayerNodeIdentifier::new_unchecked(3)); + assert_eq!(root.children(document_metadata).collect::>(), vec![LayerNodeIdentifier::new_unchecked(3)]); + root.push_child(document_metadata, LayerNodeIdentifier::new_unchecked(6)); + assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![3, 6]); + assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![3, 6]); + LayerNodeIdentifier::new_unchecked(3).add_after(document_metadata, LayerNodeIdentifier::new_unchecked(4)); + LayerNodeIdentifier::new_unchecked(3).add_before(document_metadata, LayerNodeIdentifier::new_unchecked(2)); + LayerNodeIdentifier::new_unchecked(6).add_before(document_metadata, LayerNodeIdentifier::new_unchecked(5)); + LayerNodeIdentifier::new_unchecked(6).add_after(document_metadata, LayerNodeIdentifier::new_unchecked(9)); + LayerNodeIdentifier::new_unchecked(6).push_child(document_metadata, LayerNodeIdentifier::new_unchecked(8)); + LayerNodeIdentifier::new_unchecked(6).push_front_child(document_metadata, LayerNodeIdentifier::new_unchecked(7)); + root.push_front_child(document_metadata, LayerNodeIdentifier::new_unchecked(1)); + assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![1, 2, 3, 4, 5, 6, 9]); + assert_eq!( + root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), + vec![1, 2, 3, 4, 5, 6, 7, 8, 9] + ); + assert_eq!( + root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::>(), + vec![9, 8, 7, 6, 5, 4, 3, 2, 1] + ); + assert!(root.children(document_metadata).all(|child| child.parent(document_metadata) == Some(root))); + LayerNodeIdentifier::new_unchecked(6).delete(document_metadata); + LayerNodeIdentifier::new_unchecked(1).delete(document_metadata); + assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![2, 3, 4, 5, 9]); + assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![2, 3, 4, 5, 9]); + assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::>(), vec![9, 5, 4, 3, 2]); +} diff --git a/document-legacy/src/lib.rs b/document-legacy/src/lib.rs index b0e232e24c..dcb8e9c903 100644 --- a/document-legacy/src/lib.rs +++ b/document-legacy/src/lib.rs @@ -6,6 +6,7 @@ pub mod boolean_ops; /// Contains constant values used by this crate. pub mod consts; pub mod document; +pub mod document_metadata; /// Defines errors that can occur when using this crate. pub mod error; /// Utilities for computing intersections. diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 8d7ddc1652..350ab6d9c5 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -36,6 +36,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Artboard( ArtboardMessageDiscriminant::RenderArtboards, ))), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))), + MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( + PropertiesPanelMessageDiscriminant::ResendActiveProperties, + ))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure), diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 76fb424bf9..01f45f88b0 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -232,6 +232,10 @@ pub enum FrontendMessage { UpdateNodeGraphSelection { selected: Vec, }, + UpdateNodeThumbnail { + id: NodeId, + value: String, + }, UpdateNodeTypes { #[serde(rename = "nodeTypes")] node_types: Vec, diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index e3b8c71540..96b13234b9 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -195,6 +195,9 @@ pub enum DocumentMessage { folder_path: Vec, }, UngroupSelectedLayers, + UpdateDocumentTransform { + transform: glam::DAffine2, + }, UpdateLayerMetadata { layer_path: Vec, layer_metadata: LayerMetadata, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 1e88bbbbcb..e4e808b88a 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -18,8 +18,9 @@ use crate::messages::tool::utility_types::ToolType; use crate::node_graph_executor::NodeGraphExecutor; use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::blend_mode::BlendMode; -use document_legacy::layers::folder_layer::FolderLayer; + use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant}; use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::style::{RenderData, ViewMode}; @@ -211,7 +212,6 @@ impl MessageHandler { responses.add_front(DocumentOperation::DeleteLayer { path: layer_path.clone() }); + responses.add(GraphOperationMessage::DeleteLayer { id: layer_path[0] }); responses.add_front(BroadcastEvent::ToolAbort); responses.add(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }); } @@ -938,6 +939,11 @@ impl MessageHandler { + self.document_legacy.metadata.document_to_viewport = transform; + let transform = graphene_core::renderer::format_transform_matrix(transform); + responses.add(FrontendMessage::UpdateDocumentTransform { transform }); + } UpdateLayerMetadata { layer_path, layer_metadata } => { self.layer_metadata.insert(layer_path, layer_metadata); } @@ -997,6 +1003,9 @@ impl MessageHandler &NodeNetwork { + &self.document_legacy.document_network + } pub fn rasterize_region_below_layer(&mut self, document_id: u64, layer_path: Vec, _preferences: &PreferencesMessageHandler, persistent_data: &PersistentData) -> Option { // Prepare the node graph input image @@ -1117,7 +1126,7 @@ impl DocumentMessageHandler { pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self { let mut document = Self { name, ..Self::default() }; let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); - document.document_legacy.root.transform = starting_root_transform; + document.document_legacy.metadata.document_to_viewport = starting_root_transform; document.artboard_message_handler.artboards_document.root.transform = starting_root_transform; document @@ -1221,13 +1230,14 @@ impl DocumentMessageHandler { ) } - fn serialize_structure(&self, folder: &FolderLayer, structure: &mut Vec, data: &mut Vec, path: &mut Vec) { + fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec, data: &mut Vec, path: &mut Vec) { let mut space = 0; - for (id, layer) in folder.layer_ids.iter().zip(folder.layers()).rev() { - data.push(*id); + for layer_node in folder.children(&self.document_legacy.metadata) { + data.push(layer_node.to_node()); + info!("Pushed child"); space += 1; - if let LayerDataType::Folder(ref folder) = layer.data { - path.push(*id); + if layer_node.has_children(&self.document_legacy.metadata) { + path.push(layer_node.to_node()); if self.layer_metadata(path).expanded { structure.push(space); self.serialize_structure(folder, structure, data, path); @@ -1273,7 +1283,7 @@ impl DocumentMessageHandler { /// ``` pub fn serialize_root(&self) -> Vec { let (mut structure, mut data) = (vec![0], Vec::new()); - self.serialize_structure(self.document_legacy.root.as_folder().unwrap(), &mut structure, &mut data, &mut vec![]); + self.serialize_structure(self.document_legacy.metadata.root(), &mut structure, &mut data, &mut vec![]); structure[0] = structure.len() as u64 - 1; structure.extend(data); diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 1b336c75c8..6c48b76c4b 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -9,8 +9,6 @@ use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::document::Document; -use document_legacy::Operation as DocumentOperation; -use graphene_core::renderer::format_transform_matrix; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -407,21 +405,9 @@ impl NavigationMessageHandler { fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque) { let half_viewport = viewport_bounds.size() / 2.; let scaled_half_viewport = half_viewport / self.snapped_scale(); - responses.add(DocumentOperation::SetLayerTransform { - path: vec![], - transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), - }); - responses.add(ArtboardMessage::DispatchOperation( - DocumentOperation::SetLayerTransform { - path: vec![], - transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), - } - .into(), - )); - let transform = format_transform_matrix(self.calculate_offset_transform(scaled_half_viewport)); - responses.add(FrontendMessage::UpdateDocumentTransform { transform }); - // TODO: Artboard pos + let transform = self.calculate_offset_transform(scaled_half_viewport); + responses.add(DocumentMessage::UpdateDocumentTransform { transform }); } pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message { 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 index e3f5ac9a9d..ef9af0bca8 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs @@ -1,5 +1,6 @@ use crate::messages::prelude::*; +use bezier_rs::Subpath; use graph_craft::document::NodeId; use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::brush_stroke::BrushStroke; @@ -58,12 +59,16 @@ pub enum GraphOperationMessage { id: NodeId, artboard: Artboard, }, + NewVectorLayer { + id: NodeId, + subpaths: Vec>, + }, ResizeArtboard { id: NodeId, location: IVec2, dimensions: IVec2, }, - DeleteArtboard { + DeleteLayer { id: NodeId, }, ClearArtboards, 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 index 07286e4993..86c1defd9d 100644 --- 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 @@ -1,10 +1,13 @@ use super::{resolve_document_node_type, VectorDataModification}; use crate::messages::prelude::*; +use bezier_rs::Subpath; use document_legacy::document::Document; +use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput}; +use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput}; +use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::style::{Fill, FillType, Stroke}; use graphene_core::Artboard; @@ -24,22 +27,11 @@ struct ModifyInputsContext<'a> { layer: &'a [LayerId], outwards_links: HashMap>, layer_node: Option, + document_metadata: &'a mut DocumentMetadata, } 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_layer_network_mut().ok()).map(|network| Self { - outwards_links: network.collect_outwards_links(), - network, - node_graph, - responses, - layer, - layer_node: None, - }) - } - - /// Get the node network from the document - fn new_doc(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque) -> Self { + fn new(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque) -> Self { Self { outwards_links: document.document_network.collect_outwards_links(), network: &mut document.document_network, @@ -47,15 +39,22 @@ impl<'a> ModifyInputsContext<'a> { responses, layer: &[], layer_node: None, + document_metadata: &mut document.metadata, } } - fn locate_layer(&mut self, mut id: NodeId) -> Option { - while self.network.nodes.get(&id)?.name != "Layer" { - id = self.outwards_links.get(&id)?.first().copied()?; + /// Get the node network from the document + fn new_layer(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque) -> Option { + let mut document = Self::new(document, node_graph, responses); + let Some(mut id) = layer.last().copied() else { + error!("Tried to modify root layer"); + return None; + }; + while document.network.nodes.get(&id)?.name != "Layer" { + id = document.outwards_links.get(&id)?.first().copied()?; } - self.layer_node = Some(id); - Some(id) + document.layer_node = Some(id); + Some(document) } /// Updates the input of an existing node @@ -64,8 +63,8 @@ impl<'a> ModifyInputsContext<'a> { update_input(&mut document_node.inputs); } - pub fn insert_between(&mut self, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize) -> Option { - let id = generate_uuid(); + pub fn insert_between(&mut self, id: NodeId, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize, shift_upstream: IVec2) -> Option { + assert!(!self.network.nodes.contains_key(&id), "Creating already existing node"); let pre_node = self.network.nodes.get_mut(&pre.node_id)?; node.metadata.position = pre_node.metadata.position; @@ -75,12 +74,13 @@ impl<'a> ModifyInputsContext<'a> { self.network.nodes.insert(id, node); - self.shift_upstream(id, IVec2::new(-8, 0)); + self.shift_upstream(id, shift_upstream); Some(id) } pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option { + assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing node"); let post_node = self.network.nodes.get_mut(&node_id)?; post_node.inputs[input_index] = NodeInput::node(new_id, 0); @@ -90,32 +90,39 @@ impl<'a> ModifyInputsContext<'a> { Some(new_id) } - pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId) -> Option { - let mut current_node = output_node_id; - let mut input_index = 0; - let mut current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index]; + pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize) -> Option { + assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing layer"); - while let NodeInput::Node { node_id, output_index, .. } = current_input { + let output = NodeOutput::new(output_node_id, input_index); + // Locate the node output of the first sibling layer to the new layer + let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] { let sibling_node = &self.network.nodes.get(node_id)?; - if sibling_node.name == "Layer" { - current_node = *node_id; - input_index = 7; - current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index]; + let sibling_layer = if sibling_node.name == "Layer" { + // There is already a layer node + NodeOutput::new(*node_id, 0) } else { - // Insert a layer node between the output and the new - let layer_node = resolve_document_node_type("Layer").expect("Layer node"); - let node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::default()); - let node_id = self.insert_between(NodeOutput::new(*node_id, *output_index), NodeOutput::new(current_node, input_index), node, 0, 0)?; - current_node = node_id; - input_index = 7; - current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index]; - } + // The user has connected another node to the output. Insert a layer node between the output and the node. + let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); + let node_id = self.insert_between(generate_uuid(), NodeOutput::new(*node_id, *output_index), output, node, 0, 0, IVec2::new(-8, 0))?; + NodeOutput::new(node_id, 0) + }; + + let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); + self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(-4, 3)) + } else { + let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node(); + self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-4, 3)) + }; + + // Update the document metadata structure + if let Some(new_id) = new_id { + let parent = LayerNodeIdentifier::new(output_node_id, self.network); + let new_child = LayerNodeIdentifier::new(new_id, self.network); + parent.push_front_child(self.document_metadata, new_child); + self.responses.add(DocumentMessage::DocumentStructureChanged); } - let layer_node = resolve_document_node_type("Layer").expect("Node").to_document_node_default_inputs([], Default::default()); - let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(-4, 3))?; - - Some(layer_node) + new_id } fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option { @@ -129,9 +136,30 @@ impl<'a> ModifyInputsContext<'a> { ], Default::default(), ); + self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0)) } + fn insert_vector_data(&mut self, subpaths: Vec>, layer: NodeId) { + let shape = { + let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist"); + node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default()) + }; + let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node(); + let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_document_node(); + let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node(); + + let stroke_id = generate_uuid(); + self.insert_node_before(stroke_id, layer, 0, stroke, IVec2::new(-8, 0)); + let fill_id = generate_uuid(); + self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0)); + let transform_id = generate_uuid(); + self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0)); + let shape_id = generate_uuid(); + self.insert_node_before(shape_id, transform_id, 0, shape, IVec2::new(-8, 0)); + self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); + } + fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) { let mut shift_nodes = HashSet::new(); let mut stack = vec![node_id]; @@ -184,7 +212,6 @@ impl<'a> ModifyInputsContext<'a> { } else { self.modify_new_node(name, update_input); } - self.node_graph.update_layer_path(Some(self.layer.to_vec()), self.responses); self.node_graph.nested_path.clear(); self.responses.add(PropertiesPanelMessage::ResendActiveProperties); let layer_path = self.layer.to_vec(); @@ -354,13 +381,19 @@ impl<'a> ModifyInputsContext<'a> { let mut new_input = None; let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied()); let mut delete_nodes = vec![id]; + let mut is_artboard = false; for (node, id) in self.network.primary_flow_from_opt(Some(id)) { delete_nodes.push(id); if node.name == "Artboard" { new_input = Some(node.inputs[0].clone()); + is_artboard = true; break; } } + if !is_artboard { + LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata); + } + self.responses.add(DocumentMessage::DocumentStructureChanged); for node_id in delete_nodes { self.network.nodes.remove(&node_id); @@ -378,19 +411,19 @@ impl MessageHandler, (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) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.fill_set(fill); } else { responses.add(Operation::SetLayerFill { path: layer, fill }); } } GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => { - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.update_bounds(old_bounds, new_bounds); } } GraphOperationMessage::StrokeSet { layer, stroke } => { - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.stroke_set(stroke); } else { responses.add(Operation::SetLayerStroke { path: layer, stroke }); @@ -402,9 +435,9 @@ impl MessageHandler { - let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); + let parent_transform = document.metadata.document_to_viewport * 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) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender); } @@ -424,10 +457,10 @@ impl MessageHandler { - let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); + let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); let current_transform = document.layer(&layer).ok().map(|layer| layer.transform); let bounds = LayerBounds::new(document, &layer); - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender); } let transform = transform.to_cols_array(); @@ -442,7 +475,7 @@ impl MessageHandler { let bounds = LayerBounds::new(document, &layer); - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.pivot_set(pivot, bounds); } @@ -450,33 +483,38 @@ impl MessageHandler { - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.vector_modify(modification); } } GraphOperationMessage::Brush { layer, strokes } => { - if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.brush_modify(strokes); } } GraphOperationMessage::NewArtboard { id, artboard } => { - let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); - if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id) { + let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { modify_inputs.insert_artboard(artboard, layer); } } + GraphOperationMessage::NewVectorLayer { id, subpaths } => { + let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) { + modify_inputs.insert_vector_data(subpaths, layer); + } + } GraphOperationMessage::ResizeArtboard { id, location, dimensions } => { - let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); - if modify_inputs.locate_layer(id).is_some() { + if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&[id], document, node_graph, responses) { modify_inputs.resize_artboard(location, dimensions); } } - GraphOperationMessage::DeleteArtboard { id } => { - let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); + GraphOperationMessage::DeleteLayer { id } => { + let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); modify_inputs.delete_layer(id); } GraphOperationMessage::ClearArtboards => { - let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses); + let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); let artboard_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.name == "Artboard").map(|(id, _)| *id).collect::>(); for id in artboard_nodes { modify_inputs.delete_layer(id); 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 c23284b281..4d00939d39 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 @@ -2,7 +2,7 @@ pub use self::document_node_types::*; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use crate::node_graph_executor::{GraphIdentifier, NodeGraphExecutor}; +use crate::node_graph_executor::GraphIdentifier; use document_legacy::document::Document; use document_legacy::LayerId; @@ -87,8 +87,6 @@ pub struct FrontendNode { pub position: (i32, i32), pub disabled: bool, pub previewed: bool, - #[serde(rename = "thumbnailSvg")] - pub thumbnail_svg: Option, } // (link_start, link_end, link_end_input_index) @@ -128,11 +126,6 @@ pub struct NodeGraphMessageHandler { } impl NodeGraphMessageHandler { - pub fn update_layer_path(&mut self, layer_path: Option>, responses: &mut VecDeque) { - self.layer_path = layer_path; - responses.add(NodeGraphMessage::UpdateNewNodeGraph); - } - fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork { self.layer_path .as_ref() @@ -284,7 +277,7 @@ impl NodeGraphMessageHandler { } } - fn send_graph(network: &NodeNetwork, executor: &NodeGraphExecutor, layer_path: &Option>, responses: &mut VecDeque) { + fn send_graph(network: &NodeNetwork, layer_path: &Option>, responses: &mut VecDeque) { responses.add(PropertiesPanelMessage::ResendActiveProperties); let layer_id = layer_path.as_ref().and_then(|path| path.last().copied()); @@ -345,8 +338,7 @@ impl NodeGraphMessageHandler { }); let primary_output = outputs.next(); - let graph_identifier = GraphIdentifier::new(layer_id); - let thumbnail_svg = executor.thumbnails.get(&graph_identifier).and_then(|thumbnails| thumbnails.get(id)).map(|svg| svg.to_string()); + let _graph_identifier = GraphIdentifier::new(layer_id); nodes.push(FrontendNode { id: *id, @@ -358,7 +350,6 @@ impl NodeGraphMessageHandler { position: node.metadata.position.into(), previewed: network.outputs_contain(*id), disabled: network.disabled.contains(id), - thumbnail_svg, }) } responses.add(FrontendMessage::UpdateNodeGraph { nodes, links }); @@ -467,7 +458,6 @@ impl NodeGraphMessageHandler { #[derive(Debug)] pub struct NodeGraphHandlerData<'a> { pub document: &'a mut Document, - pub executor: &'a NodeGraphExecutor, pub document_id: u64, pub document_name: &'a str, pub input: &'a InputPreprocessorMessageHandler, @@ -478,9 +468,7 @@ impl<'a> MessageHandler> for NodeGrap fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) { #[remain::sorted] match message { - NodeGraphMessage::CloseNodeGraph => { - self.update_layer_path(None, responses); - } + NodeGraphMessage::CloseNodeGraph => {} NodeGraphMessage::ConnectNodesByLink { output_node, output_node_connector_index, @@ -571,6 +559,8 @@ impl<'a> MessageHandler> for NodeGrap if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + responses.add(NodeGraphMessage::RunDocumentGraph); } } } @@ -610,7 +600,7 @@ impl<'a> MessageHandler> for NodeGrap } } if let Some(network) = self.get_active_network(data.document) { - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); } self.collect_nested_addresses(data.document, data.document_name, responses); self.update_selected(data.document, responses); @@ -635,7 +625,7 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::InsertNode { node_id, document_node }); } - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); self.update_selected(data.document, responses); responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } @@ -646,7 +636,7 @@ impl<'a> MessageHandler> for NodeGrap self.nested_path.pop(); } if let Some(network) = self.get_active_network(data.document) { - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); } self.collect_nested_addresses(data.document, data.document_name, responses); self.update_selected(data.document, responses); @@ -697,7 +687,7 @@ impl<'a> MessageHandler> for NodeGrap node.metadata.position += IVec2::new(displacement_x, displacement_y) } } - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); } NodeGraphMessage::OpenNodeGraph { layer_path } => { self.layer_path = Some(layer_path); @@ -705,7 +695,7 @@ impl<'a> MessageHandler> for NodeGrap if let Some(network) = self.get_active_network(data.document) { self.selected_nodes.clear(); - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); let node_types = document_node_types::collect_node_types(); responses.add(FrontendMessage::UpdateNodeTypes { node_types }); @@ -774,7 +764,7 @@ impl<'a> MessageHandler> for NodeGrap } NodeGraphMessage::SendGraph { should_rerender } => { if let Some(network) = self.get_active_network(data.document) { - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); if should_rerender { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); @@ -901,12 +891,14 @@ impl<'a> MessageHandler> for NodeGrap .disabled .extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id))); } - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); // Only generate node graph if one of the selected nodes is connected to the output if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + responses.add(NodeGraphMessage::RunDocumentGraph); } } } @@ -927,18 +919,20 @@ impl<'a> MessageHandler> for NodeGrap } else { return; } - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); } self.update_selection_action_buttons(data.document, responses); if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + responses.add(NodeGraphMessage::RunDocumentGraph); } } NodeGraphMessage::UpdateNewNodeGraph => { if let Some(network) = self.get_active_network(data.document) { self.selected_nodes.clear(); - Self::send_graph(network, data.executor, &self.layer_path, responses); + Self::send_graph(network, &self.layer_path, responses); let node_types = document_node_types::collect_node_types(); responses.add(FrontendMessage::UpdateNodeTypes { node_types }); 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 64edfc3753..e51dc41da2 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 @@ -181,23 +181,14 @@ fn static_nodes() -> Vec { name: "Layer", category: "General", identifier: NodeImplementation::DocumentNode(NodeNetwork { - inputs: vec![0; 8], - outputs: vec![NodeOutput::new(1, 0)], + inputs: vec![0, 2, 2, 2, 2, 2, 2, 2], + outputs: vec![NodeOutput::new(2, 0)], nodes: [ ( 0, DocumentNode { - inputs: vec![ - NodeInput::Network(concrete!(graphene_core::vector::VectorData)), - NodeInput::Network(concrete!(String)), - NodeInput::Network(concrete!(BlendMode)), - NodeInput::Network(concrete!(f32)), - NodeInput::Network(concrete!(bool)), - NodeInput::Network(concrete!(bool)), - NodeInput::Network(concrete!(bool)), - NodeInput::Network(concrete!(graphene_core::GraphicGroup)), - ], - implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"), + inputs: vec![NodeInput::Network(concrete!(graphene_core::vector::VectorData))], + implementation: DocumentNodeImplementation::proto("graphene_core::ToGraphicElementData"), ..Default::default() }, ), @@ -211,6 +202,23 @@ fn static_nodes() -> Vec { ..Default::default() }, ), + ( + 2, + DocumentNode { + inputs: vec![ + NodeInput::node(1, 0), + NodeInput::Network(concrete!(String)), + NodeInput::Network(concrete!(BlendMode)), + NodeInput::Network(concrete!(f32)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(bool)), + NodeInput::Network(concrete!(graphene_core::GraphicGroup)), + ], + implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>"), + ..Default::default() + }, + ), ] .into(), ..Default::default() @@ -2390,6 +2398,11 @@ impl DocumentNodeType { let inputs = self.inputs.iter().map(|default| input_override.next().unwrap_or_default().unwrap_or_else(|| default.default.clone())); self.to_document_node(inputs, metadata) } + + /// Converts the [DocumentNodeType] type to a [DocumentNode], completly default + pub fn default_document_node(&self) -> DocumentNode { + self.to_document_node(self.inputs.iter().map(|input| input.default.clone()), DocumentNodeMetadata::default()) + } } pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index c46fa4ed4d..06aadf4d0b 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -17,7 +17,6 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_core::text::Font; -use glam::DAffine2; use std::sync::Arc; #[derive(Debug, Default)] @@ -515,7 +514,7 @@ impl MessageHandler { if let Some(node_id) = node_id { - self.executor.insert_thumbnail_blob_url(blob_url, layer_path.last().copied(), node_id, responses); + self.executor.insert_thumbnail_blob_url(blob_url, node_id, responses); return; } let message = DocumentMessage::SetImageBlobUrl { @@ -713,8 +712,12 @@ impl PortfolioMessageHandler { } pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque) { - let transform = self.active_document().map(|document| document.document_legacy.root.transform).unwrap_or(DAffine2::IDENTITY); - self.executor.poll_node_graph_evaluation(transform, responses).unwrap_or_else(|e| { + let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else { + warn!("Polling node graph with no document"); + return; + }; + + self.executor.poll_node_graph_evaluation(&mut active_document.document_legacy, responses).unwrap_or_else(|e| { log::error!("Error while evaluating node graph: {}", e); }); } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 7bef3c6225..4f5ad90faf 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -1,10 +1,9 @@ -use crate::messages::portfolio::document::node_graph; use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; use bezier_rs::{ManipulatorGroup, Subpath}; -use document_legacy::{LayerId, Operation}; -use graph_craft::document::NodeNetwork; +use document_legacy::{document_metadata::LayerNodeIdentifier, LayerId, Operation}; +use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork}; use graphene_core::uuid::ManipulatorGroupId; use glam::DAffine2; @@ -12,8 +11,10 @@ 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) { - let network = node_graph::new_vector_network(subpaths); - new_custom_layer(network, layer_path, responses); + responses.add(GraphOperationMessage::NewVectorLayer { + id: *layer_path.last().unwrap(), + subpaths, + }); } pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec, responses: &mut VecDeque) { @@ -38,3 +39,63 @@ pub fn set_manipulator_mirror_angle(manipulator_groups: &Vec { + node_graph: &'a NodeNetwork, + outwards_links: HashMap>, + layer_node: NodeId, +} + +impl<'a> NodeGraphLayer<'a> { + /// Get the layer node from the document + pub fn new(layer: LayerNodeIdentifier, document: &'a document_legacy::document::Document) -> Option { + let node_graph = &document.document_network; + let outwards_links = document.document_network.collect_outwards_links(); + + Some(Self { + node_graph, + outwards_links, + layer_node: layer.to_node(), + }) + } + + /// Get the nearest layer node from the path and the document + pub fn new_from_path(layer: &[LayerId], document: &'a document_legacy::document::Document) -> Option { + let node_graph = &document.document_network; + let outwards_links = document.document_network.collect_outwards_links(); + + let Some(mut layer_node) = layer.last().copied() else { + error!("Tried to modify root layer"); + return None; + }; + while node_graph.nodes.get(&layer_node)?.name != "Layer" { + layer_node = outwards_links.get(&layer_node)?.first().copied()?; + } + Some(Self { + node_graph, + outwards_links, + layer_node, + }) + } + + /// Return an iterator up the primary flow of the layer + pub fn primary_layer_flow(&self) -> impl Iterator { + self.node_graph.primary_flow_from_opt(Some(self.layer_node)) + } + + /// Find a specific input of a node within the layer's primary flow + pub fn find_input(&self, node_name: &str, index: usize) -> Option<&'a TaggedValue> { + for (node, _id) in self.primary_layer_flow() { + if node.name == node_name { + let subpaths_input = node.inputs.get(index)?; + let NodeInput::Value { tagged_value, .. } = subpaths_input else { + continue; + }; + + return Some(tagged_value); + } + } + None + } +} diff --git a/editor/src/messages/tool/common_functionality/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs index 65500fbbc7..7ebb108100 100644 --- a/editor/src/messages/tool/common_functionality/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -3,9 +3,11 @@ use crate::application::generate_uuid; 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 crate::messages::tool::tool_messages::pen_tool::{get_manipulator_groups, get_subpaths}; use bezier_rs::ManipulatorGroup; use document_legacy::document::Document; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::style::{self, Fill, Stroke}; use document_legacy::{LayerId, Operation}; use graphene_core::raster::color::Color; @@ -35,8 +37,8 @@ const POINT_STROKE_WEIGHT: f64 = 2.; #[derive(Clone, Debug, Default)] pub struct OverlayRenderer { - shape_overlay_cache: HashMap>, - manipulator_group_overlay_cache: HashMap>, + shape_overlay_cache: HashMap>, + manipulator_group_overlay_cache: HashMap>, } impl OverlayRenderer { @@ -44,116 +46,116 @@ impl OverlayRenderer { Self::default() } - 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); + pub fn query_cache(&self, layer: &LayerNodeIdentifier) -> Option<&Vec> { + self.shape_overlay_cache.get(layer) + } - 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); + pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque) { + let transform = document.metadata.transform_from_viewport(layer); - // Create an outline if we do not have a cached one - if outline_cache.is_none() { - let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_rs(&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(), graphene_core::vector::Subpath::from_bezier_rs(&vector_data.subpaths), responses); - Self::place_outline_overlays(outline_path.clone(), &transform, responses); - } + let Some(subpaths) = get_subpaths(layer, document) else { + return; + }; - // Create, place, and style the manipulator overlays - for manipulator_group in vector_data.manipulator_groups() { - let manipulator_group_cache = self.manipulator_group_overlay_cache.entry(*layer_id).or_default().entry(manipulator_group.id).or_default(); + self.layer_overlay_visibility(document, layer, true, responses); - // Only view in and out handles if they are not on top of the anchor - let [in_handle, out_handle] = { - let anchor = manipulator_group.anchor; + let outline_cache = self.shape_overlay_cache.get(&layer); + trace!("Overlay: Outline cache {:?}", &outline_cache); - 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 an outline if we do not have a cached one + if outline_cache.is_none() { + let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_rs(subpaths), responses); + self.shape_overlay_cache.insert(layer, 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); + Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_rs(subpaths), responses); + Self::place_outline_overlays(outline_path.clone(), &transform, responses); + } - // Create anchor - 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.in_handle.take(), responses); - Self::remove_overlay(manipulator_group_cache.in_line.take(), responses); + // Create, place, and style the manipulator overlays + for manipulator_group in get_manipulator_groups(subpaths) { + let manipulator_group_cache = self.manipulator_group_overlay_cache.entry(layer).or_default().entry(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 anchor = manipulator_group.anchor; + + 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.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.in_handle.take(), responses); + Self::remove_overlay(manipulator_group_cache.in_line.take(), responses); + } else { + 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.out_handle.take(), responses); + Self::remove_overlay(manipulator_group_cache.out_line.take(), responses); + } else { + 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(selected_shape_state, layer, manipulator_group, manipulator_group_cache, responses); + } + + if let Some(layer_overlays) = self.manipulator_group_overlay_cache.get_mut(&layer) { + if layer_overlays.len() > subpaths.iter().map(|subpath| subpath.len()).sum() { + layer_overlays.retain(|manipulator, manipulator_group_overlays| { + if get_manipulator_groups(subpaths).any(|current_manipulator| current_manipulator.id == *manipulator) { + true } else { - 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)); + Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); + false } - // Create or delete out handle - if out_handle.is_none() { - Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses); - Self::remove_overlay(manipulator_group_cache.out_line.take(), responses); - } else { - 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(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses); - } - - if let Some(layer_overlays) = self.manipulator_group_overlay_cache.get_mut(layer_id) { - if layer_overlays.len() > vector_data.manipulator_groups().count() { - layer_overlays.retain(|manipulator, manipulator_group_overlays| { - if vector_data.manipulator_groups().any(|current_manipulator| current_manipulator.id == *manipulator) { - true - } else { - Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); - false - } - }); - } - } - // TODO Handle removing shapes from cache so we don't memory leak - // Eventually will get replaced with am immediate mode renderer for overlays + }); } } + // TODO Handle removing shapes from cache so we don't memory leak + // Eventually will get replaced with am immediate mode renderer for overlays + responses.add(OverlaysMessage::Rerender); } - pub fn clear_subpath_overlays(&mut self, _document: &Document, layer_path: Vec, responses: &mut VecDeque) { - let layer_id = layer_path.last().unwrap(); - + pub fn clear_subpath_overlays(&mut self, _document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque) { // Remove the shape outline overlays - if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { + if let Some(overlay_path) = self.shape_overlay_cache.get(&layer) { Self::remove_outline_overlays(overlay_path.clone(), responses) } - self.shape_overlay_cache.remove(layer_id); + self.shape_overlay_cache.remove(&layer); // Remove the ManipulatorGroup overlays - let Some(layer_cache) = self.manipulator_group_overlay_cache.remove(layer_id) else { return }; + let Some(layer_cache) = self.manipulator_group_overlay_cache.remove(&layer) else { return }; for manipulator_group_overlays in layer_cache.values() { Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); } } - pub fn layer_overlay_visibility(&mut self, document: &Document, layer_path: Vec, visibility: bool, responses: &mut VecDeque) { - let layer_id = layer_path.last().unwrap(); - + pub fn layer_overlay_visibility(&mut self, document: &Document, layer: LayerNodeIdentifier, visibility: bool, responses: &mut VecDeque) { // Hide the shape outline overlays - if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { + if let Some(overlay_path) = self.shape_overlay_cache.get(&layer) { Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses); } // Hide the manipulator group overlays - let Some(manipulator_groups) = self.manipulator_group_overlay_cache.get(layer_id) else { return }; + let Some(manipulator_groups) = self.manipulator_group_overlay_cache.get(&layer) else { return }; if visibility { - let Ok(layer) = document.layer(&layer_path) else { return }; - let Some(vector_data) = layer.as_vector_data() else { return }; - for manipulator_group in vector_data.manipulator_groups() { + let Some(subpaths) = get_subpaths(layer, document) else { return }; + for manipulator_group in get_manipulator_groups(subpaths) { let id = manipulator_group.id; if let Some(manipulator_group_overlays) = manipulator_groups.get(&id) { Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses); @@ -338,11 +340,11 @@ impl OverlayRenderer { } /// Sets the overlay style for this point. - fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque) { + fn style_overlays(state: &SelectedShapeState, layer: LayerNodeIdentifier, 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(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); - let selected_shape_state = state.get(layer_path); + let selected_shape_state = state.get(&layer); // 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, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() { diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index da7ce3f5eb..a94c311150 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -20,7 +20,7 @@ impl Resize { pub fn start(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) { self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - let root_transform = document.document_legacy.root.transform; + let root_transform = document.document_legacy.metadata.document_to_viewport; self.drag_start = root_transform.inverse().transform_point2(self.snap_manager.snap_position(responses, document, input.mouse.position)); } @@ -32,7 +32,7 @@ impl Resize { /// Calculate the drag start position in viewport space. pub fn viewport_drag_start(&self, document: &DocumentMessageHandler) -> DVec2 { - let root_transform = document.document_legacy.root.transform; + let root_transform = document.document_legacy.metadata.document_to_viewport; root_transform.transform_point2(self.drag_start) } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 5a3db5711d..bc2fd3e461 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1,12 +1,13 @@ use crate::consts::DRAG_THRESHOLD; use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; +use crate::messages::tool::tool_messages::pen_tool::{get_manipulator_from_id, get_manipulator_groups, get_mirror_handles, get_subpaths}; use bezier_rs::{Bezier, ManipulatorGroup, TValue}; use document_legacy::document::Document; -use document_legacy::LayerId; +use document_legacy::document_metadata::LayerNodeIdentifier; use graphene_core::uuid::ManipulatorGroupId; -use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData}; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::DVec2; @@ -40,25 +41,25 @@ impl SelectedLayerState { } } -pub type SelectedShapeState = HashMap, SelectedLayerState>; +pub type SelectedShapeState = HashMap; #[derive(Debug, Default)] pub struct ShapeState { // The layers we can select and edit manipulators (anchors and handles) from pub selected_shape_state: SelectedShapeState, } -pub struct SelectedPointsInfo<'a> { - pub points: Vec>, +pub struct SelectedPointsInfo { + pub points: Vec, pub offset: DVec2, } #[derive(Clone, Copy, Eq, PartialEq)] -pub struct ManipulatorPointInfo<'a> { - pub shape_layer_path: &'a [LayerId], +pub struct ManipulatorPointInfo { + pub layer: LayerNodeIdentifier, pub point_id: ManipulatorPointId, } -pub type OpposingHandleLengths = HashMap, HashMap>>; +pub type OpposingHandleLengths = HashMap>>; // TODO Consider keeping a list of selected manipulators to minimize traversals of the layers impl ShapeState { @@ -69,14 +70,14 @@ impl ShapeState { return None; } - if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) { + if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) { trace!("Selecting... manipulator point: {:?}", manipulator_point_id); - 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 subpaths = get_subpaths(layer, document)?; + let manipulator_group = get_manipulator_groups(subpaths).find(|group| group.id == manipulator_point_id.group)?; let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?; - let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?; + let selected_shape_state = self.selected_shape_state.get(&layer)?; let already_selected = selected_shape_state.is_selected(manipulator_point_id); // Should we select or deselect the point? @@ -90,24 +91,21 @@ impl ShapeState { } // Add to the selected points - let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; + let selected_shape_state = self.selected_shape_state.get_mut(&layer)?; 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) - .map(|viewspace| mouse_position - viewspace.transform_point2(point_position)) - .unwrap_or_default(); + let offset = mouse_position - document.metadata.transform_from_viewport(layer).transform_point2(point_position); 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 })) + .flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id })) .collect(); return Some(SelectedPointsInfo { points, offset }); } else { - let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?; + let selected_shape_state = self.selected_shape_state.get_mut(&layer)?; selected_shape_state.deselect_point(manipulator_point_id); return None; @@ -117,14 +115,12 @@ impl ShapeState { } pub fn select_all_points(&mut self, document: &Document) { - for (layer_path, selected_layer_state) in self.selected_shape_state.iter_mut() { - let Ok(layer) = document.layer(layer_path) else { continue }; - let Some(vector_data) = layer.as_vector_data() else { continue }; - - for group in vector_data.manipulator_groups() { - selected_layer_state.select_point(ManipulatorPointId::new(group.id, SelectedType::Anchor)); + for (layer, state) in self.selected_shape_state.iter_mut() { + let Some(subpaths) = get_subpaths(*layer, document) else { return }; + for manipulator in get_manipulator_groups(subpaths) { + state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor)); for selected_type in &[SelectedType::InHandle, SelectedType::OutHandle] { - selected_layer_state.deselect_point(ManipulatorPointId::new(group.id, *selected_type)); + state.deselect_point(ManipulatorPointId::new(manipulator.id, *selected_type)); } } } @@ -135,14 +131,14 @@ impl ShapeState { } /// 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>) { + pub fn set_selected_layers(&mut self, target_layers: Vec) { 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) -> impl Iterator> { + pub fn selected_layers(&self) -> impl Iterator { self.selected_shape_state.keys() } @@ -157,15 +153,14 @@ impl ShapeState { /// 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()) + self.iter(document).flat_map(|subpaths| get_manipulator_groups(subpaths)) } // Sets the selected points to all points for the corresponding intersection - 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() { + pub fn select_all_anchors(&mut self, document: &Document, layer: LayerNodeIdentifier) { + let Some(subpaths) = get_subpaths(layer, document) else { return }; + let Some(state) = self.selected_shape_state.get_mut(&layer) else { return }; + for manipulator in get_manipulator_groups(subpaths) { state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor)) } } @@ -219,7 +214,7 @@ impl ShapeState { .selected_shape_state .iter() .filter_map(|(layer_id, selection_state)| { - let layer = document.layer(layer_id).ok()?; + let layer = document.layer(&layer_id.to_path()).ok()?; let vector_data = layer.as_vector_data()?; Some((vector_data, selection_state)) }) @@ -236,7 +231,7 @@ impl ShapeState { } } - pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath, index: usize, responses: &mut VecDeque, layer_path: &[u64]) { + pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath, index: usize, responses: &mut VecDeque, layer: &LayerNodeIdentifier) { let manipulator_groups = subpath.manipulator_groups(); let manipulator = manipulator_groups[index]; @@ -269,7 +264,7 @@ impl ShapeState { // Mirror the angle but not the distance responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorHandleMirroring { id: manipulator.id, mirror_angle: true, @@ -290,7 +285,7 @@ impl ShapeState { if let Some(in_handle) = length_previous.map(|length| anchor_position + handle_vector * length) { let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle); responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle }, }); } @@ -298,7 +293,7 @@ impl ShapeState { if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) { let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle); responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle }, }); } @@ -309,7 +304,7 @@ impl ShapeState { let mut skip_set = HashSet::new(); for (layer_id, layer_state) in self.selected_shape_state.iter() { - let layer = document.layer(layer_id).ok()?; + let layer = document.layer(&layer_id.to_path()).ok()?; let vector_data = layer.as_vector_data()?; for point in layer_state.selected_points.iter() { @@ -338,7 +333,7 @@ impl ShapeState { let out_handle = ManipulatorPointId::new(point.group, SelectedType::OutHandle); if let Some(position) = group.out_handle { responses.add(GraphOperationMessage::Vector { - layer: layer_id.to_vec(), + layer: layer_id.to_path(), modification: VectorDataModification::SetManipulatorPosition { point: out_handle, position }, }); } @@ -347,7 +342,7 @@ impl ShapeState { let in_handle = ManipulatorPointId::new(point.group, SelectedType::InHandle); if let Some(position) = group.in_handle { responses.add(GraphOperationMessage::Vector { - layer: layer_id.to_vec(), + layer: layer_id.to_path(), modification: VectorDataModification::SetManipulatorPosition { point: in_handle, position }, }); } @@ -373,11 +368,11 @@ impl ShapeState { /// Move the selected points by dragging the mouse. 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 }; + for (&layer, state) in &self.selected_shape_state { + let Some(subpaths) = get_subpaths(layer, document) else { continue }; + let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue }; - let transform = document.multiply_transforms(layer_path).unwrap_or_default(); + let transform = document.metadata.transform_from_viewport(layer); let delta = transform.inverse().transform_vector2(delta); for &point in state.selected_points.iter() { @@ -385,13 +380,13 @@ impl ShapeState { continue; } - let Some(group) = vector_data.manipulator_from_id(point.group) else { continue }; + let Some(group) =get_manipulator_from_id(subpaths,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(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }); }; @@ -404,13 +399,13 @@ impl ShapeState { } if mirror_distance && point.manipulator_type != SelectedType::Anchor { - let mut mirror = vector_data.mirror_angle.contains(&point.group); + let mut mirror = mirror_angle.contains(&point.group); // If there is no opposing handle, we mirror even if mirror_angle doesn't contain the group // and set angle mirroring to true. if !mirror && point.manipulator_type.opposite().get_position(group).is_none() { responses.add(GraphOperationMessage::Vector { - layer: layer_path.clone(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: true }, }); mirror = true; @@ -428,7 +423,7 @@ impl ShapeState { } let position = group.anchor - (original_handle_position - group.anchor); responses.add(GraphOperationMessage::Vector { - layer: layer_path.clone(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }); } @@ -439,13 +434,13 @@ impl ShapeState { /// Delete selected and mirrored handles with zero length when the drag stops. pub fn delete_selected_handles_with_zero_length(&self, document: &Document, opposing_handle_lengths: &Option, 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 }; + for (&layer, state) in &self.selected_shape_state { + let Some(subpaths) = get_subpaths(layer, document) else { continue }; + let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue }; - let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(layer_path)); + let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(&layer)); - let transform = document.multiply_transforms(layer_path).unwrap_or(glam::DAffine2::IDENTITY); + let transform = document.metadata.transform_from_viewport(layer); for &point in state.selected_points.iter() { let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor); @@ -453,7 +448,7 @@ impl ShapeState { continue; } - let Some(group) = vector_data.manipulator_from_id(point.group) else { continue }; + let Some(group) = get_manipulator_from_id(subpaths,point.group) else { continue }; let anchor_position = transform.transform_point2(group.anchor); @@ -465,17 +460,17 @@ impl ShapeState { if (anchor_position - point_position).length() < DRAG_THRESHOLD { responses.add(GraphOperationMessage::Vector { - layer: layer_path.clone(), + layer: layer.to_path(), modification: VectorDataModification::RemoveManipulatorPoint { point }, }); // Remove opposing handle if it is not selected and is mirrored. let opposite_point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite()); - if !state.is_selected(opposite_point) && vector_data.mirror_angle.contains(&point.group) { + if !state.is_selected(opposite_point) && mirror_angle.contains(&point.group) { if let Some(lengths) = opposing_handle_lengths { if lengths.contains_key(&point.group) { responses.add(GraphOperationMessage::Vector { - layer: layer_path.clone(), + layer: layer.to_path(), modification: VectorDataModification::RemoveManipulatorPoint { point: opposite_point }, }); } @@ -490,11 +485,9 @@ impl ShapeState { pub fn opposing_handle_lengths(&self, document: &Document) -> OpposingHandleLengths { self.selected_shape_state .iter() - .filter_map(|(path, state)| { - let layer = document.layer(path).ok()?; - let vector_data = layer.as_vector_data()?; - let opposing_handle_lengths = vector_data - .subpaths + .filter_map(|(&layer, state)| { + let subpaths = get_subpaths(layer, document)?; + let opposing_handle_lengths = subpaths .iter() .flat_map(|subpath| { subpath.manipulator_groups().iter().filter_map(|manipulator_group| { @@ -525,21 +518,21 @@ impl ShapeState { }) }) .collect::>(); - Some((path.clone(), opposing_handle_lengths)) + Some((layer, opposing_handle_lengths)) }) .collect::>() } /// Reset the opposing handle lengths. pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &OpposingHandleLengths, 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 }; + for (&layer, state) in &self.selected_shape_state { + let Some(subpaths) = get_subpaths(layer, document) else { continue }; + let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue }; + let Some(opposing_handle_lengths) = opposing_handle_lengths.get(&layer) else { continue }; - for subpath in &vector_data.subpaths { + for subpath in subpaths { for manipulator_group in subpath.manipulator_groups() { - if !vector_data.mirror_angle.contains(&manipulator_group.id) { + if !mirror_angle.contains(&manipulator_group.id) { continue; } @@ -563,7 +556,7 @@ impl ShapeState { let Some(opposing_handle_length) = opposing_handle_length else { responses.add(GraphOperationMessage::Vector { - layer: path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::RemoveManipulatorPoint { point: ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite()), }, @@ -582,7 +575,7 @@ impl ShapeState { assert!(position.is_finite(), "Opposing handle not finite!"); responses.add(GraphOperationMessage::Vector { - layer: path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }); } @@ -595,7 +588,7 @@ impl ShapeState { for (layer, state) in &self.selected_shape_state { for &point in &state.selected_points { responses.add(GraphOperationMessage::Vector { - layer: layer.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::RemoveManipulatorPoint { point }, }) } @@ -607,7 +600,7 @@ impl ShapeState { for (layer, state) in &self.selected_shape_state { for point in &state.selected_points { responses.add(GraphOperationMessage::Vector { - layer: layer.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group }, }) } @@ -619,7 +612,7 @@ impl ShapeState { for (layer, state) in &self.selected_shape_state { for point in &state.selected_points { responses.add(GraphOperationMessage::Vector { - layer: layer.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorHandleMirroring { id: point.group, mirror_angle }, }); } @@ -627,27 +620,24 @@ impl ShapeState { } /// Iterate over the shapes. - 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()) + pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator>> + 'a { + self.selected_shape_state.keys().filter_map(|&layer| get_subpaths(layer, document)) } /// 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]. - pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec, ManipulatorPointId)> { + pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(LayerNodeIdentifier, 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_shape_state.keys() { + 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 point: {:?}", manipulator_point_id); - return Some((layer.clone(), manipulator_point_id)); + return Some((layer, manipulator_point_id)); } } } @@ -659,20 +649,18 @@ impl ShapeState { /// 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 `(ManipulatorPointId, distance squared)`. - fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> { + fn closest_point_in_layer(document: &Document, layer: LayerNodeIdentifier, pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> { let mut closest_distance_squared: f64 = f64::MAX; let mut result = None; - 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); + let subpaths = get_subpaths(layer, document)?; + let viewspace = document.metadata.transform_from_viewport(layer); + for manipulator in get_manipulator_groups(subpaths) { + 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)); - } + if distance_squared < closest_distance_squared { + closest_distance_squared = distance_squared; + result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared)); } } @@ -680,17 +668,17 @@ impl ShapeState { } /// Find the `t` value along the path segment we have clicked upon, together with that segment ID. - 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()?; + fn closest_segment(&self, document: &Document, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> { + let transform = document.metadata.transform_from_viewport(layer); let layer_pos = transform.inverse().transform_point2(position); let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() }; let mut result = None; let mut closest_distance_squared: f64 = tolerance * tolerance; - let vector_data = document.layer(layer_path).ok()?.as_vector_data()?; + let subpaths = get_subpaths(layer, document)?; - for subpath in &vector_data.subpaths { + for subpath in subpaths { for (manipulator_index, bezier) in subpath.iter().enumerate() { let t = bezier.project(layer_pos, Some(projection_options)); let layerspace = bezier.evaluate(TValue::Parametric(t)); @@ -712,15 +700,15 @@ impl ShapeState { /// 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((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) { + for &layer in self.selected_layers() { + if let Some((start, end, bezier, t)) = self.closest_segment(document, layer, position, tolerance) { let [first, second] = bezier.split(TValue::Parametric(t)); // Adjust the first manipulator group's out handle 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(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }; responses.add(out_handle); @@ -728,7 +716,7 @@ impl ShapeState { // Insert a new manipulator group between the existing ones let manipulator_group = ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start()); let insert = GraphOperationMessage::Vector { - layer: layer_path.clone(), + layer: layer.to_path(), modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start }, }; responses.add(insert); @@ -737,7 +725,7 @@ impl ShapeState { 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(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }; responses.add(in_handle); @@ -749,15 +737,15 @@ impl ShapeState { /// 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 vector_data = document.layer(layer_path).ok()?.as_vector_data()?; + let mut process_layer = |layer| { + let subpaths = get_subpaths(layer, document)?; - let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?; + let transform_to_screenspace = document.metadata.transform_from_viewport(layer); let mut result = None; let mut closest_distance_squared = tolerance * tolerance; // Find the closest anchor point on the current layer - for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() { + for (subpath_index, subpath) in 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); @@ -771,7 +759,7 @@ impl ShapeState { let (subpath_index, index, manipulator) = result?; let anchor_position = manipulator.anchor; - let subpath = &vector_data.subpaths[subpath_index]; + let subpath = &subpaths[subpath_index]; // Check by comparing the handle positions to the anchor if this maniuplator group is a point let already_sharp = match (manipulator.in_handle, manipulator.out_handle) { @@ -781,20 +769,20 @@ impl ShapeState { }; if already_sharp { - self.smooth_manipulator_group(subpath, index, responses, layer_path); + self.smooth_manipulator_group(subpath, index, responses, &layer); } else { let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle); responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position }, }); let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle); responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position }, }); responses.add(GraphOperationMessage::Vector { - layer: layer_path.to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorHandleMirroring { id: manipulator.id, mirror_angle: false, @@ -804,8 +792,8 @@ impl ShapeState { Some(true) }; - for layer_path in self.selected_shape_state.keys() { - if let Some(result) = process_layer(layer_path) { + for &layer in self.selected_shape_state.keys() { + if let Some(result) = process_layer(layer) { return result; } } @@ -813,17 +801,16 @@ impl ShapeState { } pub fn select_all_in_quad(&mut self, document: &Document, quad: [DVec2; 2], clear_selection: bool) { - for (layer_path, state) in &mut self.selected_shape_state { + for (&layer, state) in &mut self.selected_shape_state { if clear_selection { state.clear_points() } - let Ok(layer) = document.layer(layer_path) else { continue }; - let Some(vector_data) = layer.as_vector_data() else { continue }; + let Some(subpaths) = get_subpaths(layer, document) else { continue }; - let transform = document.multiply_transforms(layer_path).unwrap_or_default(); + let transform = document.metadata.transform_from_viewport(layer); - for manipulator_group in vector_data.manipulator_groups() { + for manipulator_group in get_manipulator_groups(subpaths) { for selected_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] { let Some(position) = selected_type.get_position(manipulator_group) else { continue }; let transformed_position = transform.transform_point2(position); diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 706cff2aa6..a839a2f589 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -6,6 +6,7 @@ use crate::consts::{ }; use crate::messages::prelude::*; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::layer_info::Layer; use document_legacy::layers::style::{self, Stroke}; use document_legacy::{LayerId, Operation}; @@ -287,7 +288,12 @@ impl SnapManager { } }) .flatten() - .filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id })) + .filter(|&(point_id, _)| { + !ignore_points.contains(&ManipulatorPointInfo { + layer: LayerNodeIdentifier::from_path(path, document_message_handler.network()), + point_id, + }) + }) .map(|(_, pos)| transform.transform_point2(pos)); self.add_snap_points(document_message_handler, input, snap_points); } diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index ae4a3e62ef..e8c3a391d1 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -1,20 +1,15 @@ +use super::tool_prelude::*; use crate::application::generate_uuid; use crate::consts::SELECTION_TOLERANCE; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::misc::TargetDocument; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapManager; 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::intersection::Quad; use document_legacy::LayerId; -use glam::{DVec2, IVec2, Vec2Swizzles}; -use serde::{Deserialize, Serialize}; +use document_legacy::layers::RenderData; +use glam::{IVec2, Vec2Swizzles}; #[derive(Default)] pub struct ArtboardTool { @@ -108,306 +103,317 @@ struct ArtboardToolData { drag_current: DVec2, } +impl ArtboardToolData { + fn refresh_overlays(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) { + let current_artboard = self.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data)); + match (current_artboard, self.bounding_box_overlays.take()) { + (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses), + (Some((bounds, transform)), paths) => { + let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses)); + + bounding_box_overlays.bounds = bounds; + bounding_box_overlays.transform = transform; + + bounding_box_overlays.transform(responses); + + self.bounding_box_overlays = Some(bounding_box_overlays); + + responses.add(OverlaysMessage::Rerender); + } + _ => {} + }; + } + + fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> { + let bounding_box = self.bounding_box_overlays.as_mut()?; + let edges = bounding_box.check_selected_edges(cursor)?; + let (top, bottom, left, right) = edges; + let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); + bounding_box.opposite_pivot = selected_edges.calculate_pivot(); + + Some(edges) + } + + fn start_resizing(&mut self, selected_edges: (bool, bool, bool, bool), document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler) { + let snap_x = selected_edges.2 || selected_edges.3; + let snap_y = selected_edges.0 || selected_edges.1; + + let artboard = self.selected_artboard.unwrap(); + self.snap_manager + .start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y); + self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + + if let Some(bounds) = &mut self.bounding_box_overlays { + let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default(); + let root = document.document_legacy.metadata.document_to_viewport; + let pivot = root.inverse().transform_point2(pivot); + bounds.center_of_transformation = pivot; + } + } + + fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> bool { + responses.add(DocumentMessage::StartTransaction); + let tolerance = DVec2::splat(SELECTION_TOLERANCE); + let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); + let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data); + + responses.add(BroadcastEvent::DocumentIsDirty); + if let Some(intersection) = intersection.last() { + self.selected_artboard = Some(intersection[0]); + + self.snap_manager + .start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true); + self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + + true + } else { + self.selected_artboard = None; + + responses.add(PropertiesPanelMessage::ClearSelection); + + false + } + } + + fn resize_artboard(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) { + let Some(bounds) = &self.bounding_box_overlays else { + return; + }; + let Some(movement) = &bounds.selected_edges else { + return; + }; + + let snapped_mouse_position: DVec2 = self.snap_manager.snap_position(responses, document, mouse_position); + + let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square); + responses.add(ArtboardMessage::ResizeArtboard { + artboard: self.selected_artboard.unwrap(), + position: position.round().into(), + size: size.round().into(), + }); + responses.add(GraphOperationMessage::ResizeArtboard { + id: self.selected_artboard.unwrap(), + location: position.round().as_ivec2(), + dimensions: size.round().as_ivec2(), + }); + + responses.add(BroadcastEvent::DocumentIsDirty); + } +} + impl Fsm for ArtboardToolFsmState { type ToolData = ArtboardToolData; type ToolOptions = (); - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, - _tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - if let ToolMessage::Artboard(event) = event { - match (self, event) { - (state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => { - let current_artboard = tool_data.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data)); - match (current_artboard, tool_data.bounding_box_overlays.take()) { - (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses), - (Some((bounds, transform)), paths) => { - let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses)); + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data; - bounding_box_overlays.bounds = bounds; - bounding_box_overlays.transform = transform; + let ToolMessage::Artboard(event) = event else { + return self; + }; - bounding_box_overlays.transform(responses); + match (self, event) { + (state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => { + tool_data.refresh_overlays(document, render_data, responses); - tool_data.bounding_box_overlays = Some(bounding_box_overlays); + self + } + (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => { + tool_data.drag_start = input.mouse.position; + tool_data.drag_current = input.mouse.position; - responses.add(OverlaysMessage::Rerender); - } - _ => {} - }; - self - } - (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => { - tool_data.drag_start = input.mouse.position; - tool_data.drag_current = input.mouse.position; + if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) { + responses.add(DocumentMessage::StartTransaction); + tool_data.start_resizing(selected_edges, document, render_data, input); - let dragging_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays { - let edges = bounding_box.check_selected_edges(input.mouse.position); - - bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { - let edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); - bounding_box.opposite_pivot = edges.calculate_pivot(); - - edges - }); - - edges - } else { - None - }; - - if let Some(selected_edges) = dragging_bounds { - responses.add(DocumentMessage::StartTransaction); - - let snap_x = selected_edges.2 || selected_edges.3; - let snap_y = selected_edges.0 || selected_edges.1; - - let artboard = tool_data.selected_artboard.unwrap(); - tool_data - .snap_manager - .start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default(); - let root = document.document_legacy.root.transform; - let pivot = root.inverse().transform_point2(pivot); - bounds.center_of_transformation = pivot; - } - - ArtboardToolFsmState::ResizingBounds - } else { - responses.add(DocumentMessage::StartTransaction); - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); - let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data); - - responses.add(BroadcastEvent::DocumentIsDirty); - if let Some(intersection) = intersection.last() { - tool_data.selected_artboard = Some(intersection[0]); - - tool_data - .snap_manager - .start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - - ArtboardToolFsmState::Dragging - } else { - tool_data.selected_artboard = None; - - responses.add(PropertiesPanelMessage::ClearSelection); - - ArtboardToolFsmState::Drawing - } - } - } - (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { - if let Some(bounds) = &tool_data.bounding_box_overlays { - if let Some(movement) = &bounds.selected_edges { - let from_center = input.keyboard.get(center as usize); - let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); - - let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); - - let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square); - responses.add(ArtboardMessage::ResizeArtboard { - artboard: tool_data.selected_artboard.unwrap(), - position: position.round().into(), - size: size.round().into(), - }); - responses.add(GraphOperationMessage::ResizeArtboard { - id: tool_data.selected_artboard.unwrap(), - location: position.round().as_ivec2(), - dimensions: size.round().as_ivec2(), - }); - - responses.add(BroadcastEvent::DocumentIsDirty); - } - } ArtboardToolFsmState::ResizingBounds - } - (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => { - if let Some(bounds) = &tool_data.bounding_box_overlays { - let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); - - let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start); - let mouse_delta = mouse_position - tool_data.drag_current; - - let snap = bounds.evaluate_transform_handle_positions().into_iter().collect(); - let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); - - let size = bounds.bounds[1] - bounds.bounds[0]; - - let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move); - - responses.add(ArtboardMessage::ResizeArtboard { - artboard: tool_data.selected_artboard.unwrap(), - position: position.round().into(), - size: size.round().into(), - }); - responses.add(GraphOperationMessage::ResizeArtboard { - id: tool_data.selected_artboard.unwrap(), - location: position.round().as_ivec2(), - dimensions: size.round().as_ivec2(), - }); - - responses.add(BroadcastEvent::DocumentIsDirty); - - tool_data.drag_current = mouse_position + closest_move; - } + } else if tool_data.select_artboard(document, render_data, input, responses) { ArtboardToolFsmState::Dragging - } - (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { - let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); - - let root_transform = document.document_legacy.root.transform.inverse(); - - let mut start = tool_data.drag_start; - let mut size = snapped_mouse_position - start; - // Constrain axis - if input.keyboard.get(constrain_axis_or_aspect as usize) { - size = size.abs().max(size.abs().yx()) * size.signum(); - } - // From center - if input.keyboard.get(center as usize) { - start -= size; - size *= 2.; - } - - let start = root_transform.transform_point2(start); - let size = root_transform.transform_vector2(size); - - if let Some(artboard) = tool_data.selected_artboard { - responses.add(ArtboardMessage::ResizeArtboard { - artboard, - position: start.round().into(), - size: size.round().into(), - }); - responses.add(GraphOperationMessage::ResizeArtboard { - id: tool_data.selected_artboard.unwrap(), - location: start.round().as_ivec2(), - dimensions: size.round().as_ivec2(), - }); - } else { - let id = generate_uuid(); - tool_data.selected_artboard = Some(id); - - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - - responses.add(ArtboardMessage::AddArtboard { - id: Some(id), - position: start.round().into(), - size: (1., 1.), - }); - responses.add(GraphOperationMessage::NewArtboard { - id, - artboard: graphene_core::Artboard { - graphic_group: graphene_core::GraphicGroup::EMPTY, - location: start.round().as_ivec2(), - dimensions: IVec2::splat(1), - background: graphene_core::Color::WHITE, - clip: false, - }, - }) - } - - // Have to put message here instead of when Artboard is created - // This might result in a few more calls but it is not reliant on the order of messages - - responses.add(BroadcastEvent::DocumentIsDirty); - + } else { ArtboardToolFsmState::Drawing } - (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => { - let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false)); + } + (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { + let from_center = input.keyboard.get(center as usize); + let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); + let mouse_position = input.mouse.position; + tool_data.resize_artboard(responses, document, mouse_position, from_center, constrain_square); - if tool_data.cursor != cursor { - tool_data.cursor = cursor; - responses.add(FrontendMessage::UpdateMouseCursor { cursor }); - } + ArtboardToolFsmState::ResizingBounds + } + (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => { + if let Some(bounds) = &tool_data.bounding_box_overlays { + let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); - ArtboardToolFsmState::Ready - } - (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => { - tool_data.snap_manager.cleanup(responses); + let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start); + let mouse_delta = mouse_position - tool_data.drag_current; - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - bounds.original_transforms.clear(); - } + let snap = bounds.evaluate_transform_handle_positions().into_iter().collect(); + let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); - ArtboardToolFsmState::Ready - } - (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => { - tool_data.snap_manager.cleanup(responses); + let size = bounds.bounds[1] - bounds.bounds[0]; - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - bounds.original_transforms.clear(); - } + let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move); + + responses.add(ArtboardMessage::ResizeArtboard { + artboard: tool_data.selected_artboard.unwrap(), + position: position.round().into(), + size: size.round().into(), + }); + responses.add(GraphOperationMessage::ResizeArtboard { + id: tool_data.selected_artboard.unwrap(), + location: position.round().as_ivec2(), + dimensions: size.round().as_ivec2(), + }); responses.add(BroadcastEvent::DocumentIsDirty); - ArtboardToolFsmState::Ready + tool_data.drag_current = mouse_position + closest_move; } - (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { - tool_data.snap_manager.cleanup(responses); - - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - bounds.original_transforms.clear(); - } - - ArtboardToolFsmState::Ready - } - (_, ArtboardToolMessage::DeleteSelected) => { - if let Some(artboard) = tool_data.selected_artboard.take() { - responses.add(ArtboardMessage::DeleteArtboard { artboard }); - responses.add(GraphOperationMessage::DeleteArtboard { id: artboard }); - - responses.add(BroadcastEvent::DocumentIsDirty); - } - ArtboardToolFsmState::Ready - } - (_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => { - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - responses.add(ArtboardMessage::ResizeArtboard { - artboard: tool_data.selected_artboard.unwrap(), - position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y), - size: (bounds.bounds[1] - bounds.bounds[0]).round().into(), - }); - responses.add(GraphOperationMessage::ResizeArtboard { - id: tool_data.selected_artboard.unwrap(), - location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(), - dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(), - }); - } - - ArtboardToolFsmState::Ready - } - (_, ArtboardToolMessage::Abort) => { - if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() { - bounding_box_overlays.delete(responses); - } - - // Register properties when switching back to other tools - responses.add(PropertiesPanelMessage::SetActiveLayers { - paths: document.selected_layers().map(|path| path.to_vec()).collect(), - document: TargetDocument::Artwork, - }); - - tool_data.snap_manager.cleanup(responses); - ArtboardToolFsmState::Ready - } - _ => self, + ArtboardToolFsmState::Dragging } - } else { - self + (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { + let mouse_position = input.mouse.position; + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); + + let root_transform = document.document_legacy.metadata.document_to_viewport.inverse(); + + let mut start = tool_data.drag_start; + let mut size = snapped_mouse_position - start; + // Constrain axis + if input.keyboard.get(constrain_axis_or_aspect as usize) { + size = size.abs().max(size.abs().yx()) * size.signum(); + } + // From center + if input.keyboard.get(center as usize) { + start -= size; + size *= 2.; + } + + let start = root_transform.transform_point2(start); + let size = root_transform.transform_vector2(size); + + if let Some(artboard) = tool_data.selected_artboard { + responses.add(ArtboardMessage::ResizeArtboard { + artboard, + position: start.round().into(), + size: size.round().into(), + }); + responses.add(GraphOperationMessage::ResizeArtboard { + id: tool_data.selected_artboard.unwrap(), + location: start.round().as_ivec2(), + dimensions: size.round().as_ivec2(), + }); + } else { + let id = generate_uuid(); + tool_data.selected_artboard = Some(id); + + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true); + tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + + responses.add(ArtboardMessage::AddArtboard { + id: Some(id), + position: start.round().into(), + size: (1., 1.), + }); + responses.add(GraphOperationMessage::NewArtboard { + id, + artboard: graphene_core::Artboard { + graphic_group: graphene_core::GraphicGroup::EMPTY, + location: start.round().as_ivec2(), + dimensions: IVec2::splat(1), + background: graphene_core::Color::WHITE, + clip: false, + }, + }) + } + + // Have to put message here instead of when Artboard is created + // This might result in a few more calls but it is not reliant on the order of messages + + responses.add(BroadcastEvent::DocumentIsDirty); + + ArtboardToolFsmState::Drawing + } + (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => { + let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false)); + + if tool_data.cursor != cursor { + tool_data.cursor = cursor; + responses.add(FrontendMessage::UpdateMouseCursor { cursor }); + } + + ArtboardToolFsmState::Ready + } + (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => { + tool_data.snap_manager.cleanup(responses); + + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + bounds.original_transforms.clear(); + } + + ArtboardToolFsmState::Ready + } + (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => { + tool_data.snap_manager.cleanup(responses); + + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + bounds.original_transforms.clear(); + } + + responses.add(BroadcastEvent::DocumentIsDirty); + + ArtboardToolFsmState::Ready + } + (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { + tool_data.snap_manager.cleanup(responses); + + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + bounds.original_transforms.clear(); + } + + ArtboardToolFsmState::Ready + } + (_, ArtboardToolMessage::DeleteSelected) => { + if let Some(artboard) = tool_data.selected_artboard.take() { + responses.add(ArtboardMessage::DeleteArtboard { artboard }); + responses.add(GraphOperationMessage::DeleteLayer { id: artboard }); + + responses.add(BroadcastEvent::DocumentIsDirty); + } + ArtboardToolFsmState::Ready + } + (_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => { + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + responses.add(ArtboardMessage::ResizeArtboard { + artboard: tool_data.selected_artboard.unwrap(), + position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y), + size: (bounds.bounds[1] - bounds.bounds[0]).round().into(), + }); + responses.add(GraphOperationMessage::ResizeArtboard { + id: tool_data.selected_artboard.unwrap(), + location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(), + dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(), + }); + } + + ArtboardToolFsmState::Ready + } + (_, ArtboardToolMessage::Abort) => { + if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() { + bounding_box_overlays.delete(responses); + } + + // Register properties when switching back to other tools + responses.add(PropertiesPanelMessage::SetActiveLayers { + paths: document.selected_layers().map(|path| path.to_vec()).collect(), + document: TargetDocument::Artwork, + }); + + tool_data.snap_manager.cleanup(responses); + ArtboardToolFsmState::Ready + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index ef921274f4..f2d8d8265c 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -1,12 +1,7 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; +use super::tool_prelude::*; use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -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::layer_layer::CachedOutputData; use document_legacy::LayerId; @@ -16,9 +11,6 @@ use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_core::Color; -use glam::DAffine2; -use serde::{Deserialize, Serialize}; - const EXPOSED_BLEND_MODES: &[&[BlendMode]] = { use BlendMode::*; &[ @@ -227,43 +219,41 @@ impl LayoutHolder for BrushTool { impl<'a> MessageHandler> for BrushTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message { - match action { - BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode, - BrushToolMessageOptionsUpdate::ChangeDiameter(change) => { - let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5; - if needs_rounding && change > 0. { - self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs(); - } else if needs_rounding && change < 0. { - self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs(); - } else { - self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change; - } - self.options.diameter = self.options.diameter.max(1.); - self.send_layout(responses, LayoutTarget::ToolOptions); - } - BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter, - BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode, - BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness, - BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow, - BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing, - BrushToolMessageOptionsUpdate::Color(color) => { - self.options.color.custom_color = color; - self.options.color.color_type = ToolColorType::Custom; - } - BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type, - BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.color.primary_working_color = primary; - self.options.color.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else{ + self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); return; + }; + match action { + BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode, + BrushToolMessageOptionsUpdate::ChangeDiameter(change) => { + let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5; + if needs_rounding && change > 0. { + self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs(); + } else if needs_rounding && change < 0. { + self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs(); + } else { + self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change; + } + self.options.diameter = self.options.diameter.max(1.); + self.send_layout(responses, LayoutTarget::ToolOptions); + } + BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter, + BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode, + BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness, + BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow, + BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing, + BrushToolMessageOptionsUpdate::Color(color) => { + self.options.color.custom_color = color; + self.options.color.color_type = ToolColorType::Custom; + } + BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type, + BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.color.primary_working_color = primary; + self.options.color.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -346,94 +336,88 @@ impl Fsm for BrushToolFsmState { type ToolData = BrushToolData; type ToolOptions = BrushOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - let document_position = (document.document_legacy.root.transform).inverse().transform_point2(input.mouse.position); + } = tool_action_data; + + let document_position = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position); let layer_position = tool_data.transform.inverse().transform_point2(document_position); - if let ToolMessage::Brush(event) = event { - match (self, event) { - (BrushToolFsmState::Ready, BrushToolMessage::DragStart) => { - responses.add(DocumentMessage::StartTransaction); - let layer_path = tool_data.load_existing_strokes(document); - let new_layer = layer_path.is_none(); - if new_layer { - responses.add(DocumentMessage::DeselectAllLayers); - tool_data.layer_path = document.get_path_for_new_layer(); - } - let layer_position = tool_data.transform.inverse().transform_point2(document_position); - // TODO: Also scale it based on the input image ('Background' parameter). - // TODO: Resizing the input image results in a different brush size from the chosen diameter. - let layer_scale = 0.0001_f64 // Safety against division by zero - .max((tool_data.transform.matrix2 * glam::DVec2::X).length()) - .max((tool_data.transform.matrix2 * glam::DVec2::Y).length()); - - // Start a new stroke with a single sample - let blend_mode = match tool_options.draw_mode { - DrawMode::Draw => tool_options.blend_mode, - DrawMode::Erase => BlendMode::Erase, - DrawMode::Restore => BlendMode::Restore, - }; - tool_data.strokes.push(BrushStroke { - trace: vec![BrushInputSample { position: layer_position }], - style: BrushStyle { - color: tool_options.color.active_color().unwrap_or_default(), - diameter: tool_options.diameter / layer_scale, - hardness: tool_options.hardness, - flow: tool_options.flow, - spacing: tool_options.spacing, - blend_mode, - }, - }); - - if new_layer { - add_brush_render(tool_options, tool_data, responses); - } - tool_data.update_strokes(responses); - - BrushToolFsmState::Drawing + let ToolMessage::Brush(event) = event else { + return self; + }; + match (self, event) { + (BrushToolFsmState::Ready, BrushToolMessage::DragStart) => { + responses.add(DocumentMessage::StartTransaction); + let layer_path = tool_data.load_existing_strokes(document); + let new_layer = layer_path.is_none(); + if new_layer { + responses.add(DocumentMessage::DeselectAllLayers); + tool_data.layer_path = document.get_path_for_new_layer(); } + let layer_position = tool_data.transform.inverse().transform_point2(document_position); + // TODO: Also scale it based on the input image ('Background' parameter). + // TODO: Resizing the input image results in a different brush size from the chosen diameter. + let layer_scale = 0.0001_f64 // Safety against division by zero + .max((tool_data.transform.matrix2 * glam::DVec2::X).length()) + .max((tool_data.transform.matrix2 * glam::DVec2::Y).length()); - (BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => { - if let Some(stroke) = tool_data.strokes.last_mut() { - stroke.trace.push(BrushInputSample { position: layer_position }) - } - tool_data.update_strokes(responses); + // Start a new stroke with a single sample + let blend_mode = match tool_options.draw_mode { + DrawMode::Draw => tool_options.blend_mode, + DrawMode::Erase => BlendMode::Erase, + DrawMode::Restore => BlendMode::Restore, + }; + tool_data.strokes.push(BrushStroke { + trace: vec![BrushInputSample { position: layer_position }], + style: BrushStyle { + color: tool_options.color.active_color().unwrap_or_default(), + diameter: tool_options.diameter / layer_scale, + hardness: tool_options.hardness, + flow: tool_options.flow, + spacing: tool_options.spacing, + blend_mode, + }, + }); - BrushToolFsmState::Drawing + if new_layer { + add_brush_render(tool_options, tool_data, responses); } + tool_data.update_strokes(responses); - (BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => { - if !tool_data.strokes.is_empty() { - responses.add(DocumentMessage::CommitTransaction); - } else { - responses.add(DocumentMessage::AbortTransaction); - } - - tool_data.strokes.clear(); - - BrushToolFsmState::Ready - } - - (_, BrushToolMessage::WorkingColorChanged) => { - responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + BrushToolFsmState::Drawing } - } else { - self + + (BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => { + if let Some(stroke) = tool_data.strokes.last_mut() { + stroke.trace.push(BrushInputSample { position: layer_position }) + } + tool_data.update_strokes(responses); + + BrushToolFsmState::Drawing + } + + (BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => { + if !tool_data.strokes.is_empty() { + responses.add(DocumentMessage::CommitTransaction); + } else { + responses.add(DocumentMessage::AbortTransaction); + } + + tool_data.strokes.clear(); + + BrushToolFsmState::Ready + } + + (_, BrushToolMessage::WorkingColorChanged) => { + responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index 826d8ea2a4..d6e6355412 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -1,19 +1,11 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; +use super::tool_prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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 graphene_core::vector::style::{Fill, Stroke}; use graphene_core::Color; -use glam::DVec2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct EllipseTool { fsm_state: EllipseToolFsmState, @@ -120,33 +112,31 @@ impl LayoutHolder for EllipseTool { impl<'a> MessageHandler> for EllipseTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message { - match action { - EllipseOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - EllipseOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - EllipseOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else{ + self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); return; + }; + match action { + EllipseOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + EllipseOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + EllipseOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -192,88 +182,79 @@ impl Fsm for EllipseToolFsmState { type ToolData = EllipseToolData; type ToolOptions = EllipseToolOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, render_data, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use EllipseToolFsmState::*; - use EllipseToolMessage::*; + } = tool_action_data; let shape_data = &mut tool_data.data; - if let ToolMessage::Ellipse(event) = event { - match (self, event) { - (Drawing, CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); - self - } - (Ready, DragStart) => { - shape_data.start(responses, document, input, render_data); - responses.add(DocumentMessage::StartTransaction); - - // 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); - graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses); - - let fill_color = tool_options.fill.active_color(); - responses.add(GraphOperationMessage::FillSet { - layer: layer_path.clone(), - fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, - }); - - responses.add(GraphOperationMessage::StrokeSet { - layer: layer_path, - stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), - }); - - Drawing - } - (state, Resize { center, lock_ratio }) => { - if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) { - responses.add(message); - } - - state - } - (Drawing, DragStop) => { - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - Ready - } - (Drawing, Abort) => { - responses.add(DocumentMessage::AbortTransaction); - shape_data.cleanup(responses); - - Ready - } - (_, WorkingColorChanged) => { - responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + let ToolMessage::Ellipse(event) = event else { + return self; + }; + match (self, event) { + (EllipseToolFsmState::Drawing, EllipseToolMessage::CanvasTransformed) => { + tool_data.data.recalculate_snaps(document, input, render_data); + self } - } else { - self + (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => { + shape_data.start(responses, document, input, render_data); + responses.add(DocumentMessage::StartTransaction); + + // 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); + graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses); + + let fill_color = tool_options.fill.active_color(); + responses.add(GraphOperationMessage::FillSet { + layer: layer_path.clone(), + fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, + }); + + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path, + stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), + }); + + EllipseToolFsmState::Drawing + } + (state, EllipseToolMessage::Resize { center, lock_ratio }) => { + if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) { + responses.add(message); + } + + state + } + (EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => { + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + shape_data.cleanup(responses); + + EllipseToolFsmState::Ready + } + (EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + shape_data.cleanup(responses); + + EllipseToolFsmState::Ready + } + (_, EllipseToolMessage::WorkingColorChanged) => { + responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index af7c77dc13..88192c03a2 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,11 +1,5 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; -use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; - -use serde::{Deserialize, Serialize}; +use super::tool_prelude::*; +use crate::messages::tool::utility_types::DocumentToolData; #[derive(Default)] pub struct EyedropperTool { @@ -87,58 +81,49 @@ impl Fsm for EyedropperToolFsmState { type ToolData = EyedropperToolData; type ToolOptions = (); - fn transition( - self, - event: ToolMessage, - _tool_data: &mut Self::ToolData, - ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData, - _tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use EyedropperToolFsmState::*; - use EyedropperToolMessage::*; + fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { global_tool_data, input, .. } = tool_action_data; - if let ToolMessage::Eyedropper(event) = event { - match (self, event) { - // Ready -> Sampling - (Ready, mouse_down) | (Ready, mouse_down) if mouse_down == LeftPointerDown || mouse_down == RightPointerDown => { - update_cursor_preview(responses, input, global_tool_data, None); + let ToolMessage::Eyedropper(event) = event else{ + return self; + }; + match (self, event) { + // Ready -> Sampling + (EyedropperToolFsmState::Ready, mouse_down) if matches!(mouse_down, EyedropperToolMessage::LeftPointerDown | EyedropperToolMessage::RightPointerDown) => { + update_cursor_preview(responses, input, global_tool_data, None); - if mouse_down == LeftPointerDown { - SamplingPrimary - } else { - SamplingSecondary - } + if mouse_down == EyedropperToolMessage::LeftPointerDown { + EyedropperToolFsmState::SamplingPrimary + } else { + EyedropperToolFsmState::SamplingSecondary } - // Sampling -> Sampling - (SamplingPrimary, PointerMove) | (SamplingSecondary, PointerMove) => { - if input.viewport_bounds.in_bounds(input.mouse.position) { - update_cursor_preview(responses, input, global_tool_data, None); - } else { - disable_cursor_preview(responses); - } - - self - } - // Sampling -> Ready - (SamplingPrimary, mouse_up) | (SamplingSecondary, mouse_up) if mouse_up == LeftPointerUp || mouse_up == RightPointerUp => { - let set_color_choice = if self == SamplingPrimary { "Primary".to_string() } else { "Secondary".to_string() }; - update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice)); - disable_cursor_preview(responses); - - Ready - } - // Any -> Ready - (_, Abort) => { - disable_cursor_preview(responses); - - Ready - } - // Ready -> Ready - _ => self, } - } else { - self + // Sampling -> Sampling + (EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => { + if input.viewport_bounds.in_bounds(input.mouse.position) { + update_cursor_preview(responses, input, global_tool_data, None); + } else { + disable_cursor_preview(responses); + } + + self + } + // Sampling -> Ready + (EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::LeftPointerUp) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::RightPointerUp) => { + let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string(); + update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice)); + disable_cursor_preview(responses); + + EyedropperToolFsmState::Ready + } + // Any -> Ready + (_, EyedropperToolMessage::Abort) => { + disable_cursor_preview(responses); + + EyedropperToolFsmState::Ready + } + // Ready -> Ready + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index ec413e52d4..3fb0270066 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -1,32 +1,15 @@ -use crate::consts::SELECTION_TOLERANCE; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; -use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; - -use document_legacy::intersection::Quad; -use document_legacy::layers::layer_layer::CachedOutputData; +use super::tool_prelude::*; use document_legacy::layers::style::Fill; -use glam::DVec2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct FillTool { fsm_state: FillToolFsmState, - data: FillToolData, } #[remain::sorted] #[impl_message(Message, ToolMessage, Fill)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] pub enum FillToolMessage { - // Standard messages - #[remain::unsorted] - Abort, - // Tool-specific messages LeftPointerDown, RightPointerDown, @@ -52,7 +35,7 @@ impl LayoutHolder for FillTool { 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); + self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); } advertise_actions!(FillToolMessageDiscriminant; @@ -63,10 +46,7 @@ impl<'a> MessageHandler> for FillToo impl ToolTransition for FillTool { fn event_to_message_map(&self) -> EventToMessageMap { - EventToMessageMap { - tool_abort: Some(FillToolMessage::Abort.into()), - ..Default::default() - } + EventToMessageMap::default() } } @@ -76,71 +56,37 @@ enum FillToolFsmState { Ready, } -#[derive(Clone, Debug, Default)] -struct FillToolData {} - impl Fsm for FillToolFsmState { - type ToolData = FillToolData; + type ToolData = (); type ToolOptions = (); - fn transition( - self, - event: ToolMessage, - _tool_data: &mut Self::ToolData, - ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. - }: &mut ToolActionHandlerData, - _tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use FillToolFsmState::*; - use FillToolMessage::*; + fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { + document, global_tool_data, input, .. + } = handler_data; - if let ToolMessage::Fill(event) = event { - match (self, event) { - (Ready, lmb_or_rmb) if lmb_or_rmb == LeftPointerDown || lmb_or_rmb == RightPointerDown => { - let mouse_pos = input.mouse.position; - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); + let ToolMessage::Fill(event) = event else { + return self; + }; + let Some((layer_identifier, _)) = document.document_legacy.metadata.click(input.mouse.position) else { + return self; + }; + let layer = layer_identifier.to_path(); - if let Some(path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { - let is_bitmap = document - .document_legacy - .layer(path) - .ok() - .and_then(|layer| layer.as_layer().ok()) - .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_))); + let color = match event { + FillToolMessage::LeftPointerDown => global_tool_data.primary_color, + FillToolMessage::RightPointerDown => global_tool_data.secondary_color, + }; + let fill = Fill::Solid(color); - if is_bitmap { - return self; - } + responses.add(DocumentMessage::StartTransaction); + responses.add(DocumentMessage::SetSelectedLayers { + replacement_selected_layers: vec![layer.clone()], + }); + responses.add(GraphOperationMessage::FillSet { layer, fill }); + responses.add(DocumentMessage::CommitTransaction); - let color = match lmb_or_rmb { - LeftPointerDown => global_tool_data.primary_color, - RightPointerDown => global_tool_data.secondary_color, - Abort => unreachable!(), - }; - let fill = Fill::Solid(color); - - 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 - } - _ => self, - } - } else { - self - } + FillToolFsmState::Ready } fn update_hints(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/frame_tool.rs b/editor/src/messages/tool/tool_messages/frame_tool.rs index 9f4fde9cb2..f6abe6d122 100644 --- a/editor/src/messages/tool/tool_messages/frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/frame_tool.rs @@ -1,18 +1,10 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; +use super::tool_prelude::*; use crate::messages::portfolio::document::node_graph; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::path_outline::PathOutline; 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::Operation; -use glam::DAffine2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct FrameTool { fsm_state: NodeGraphToolFsmState, @@ -107,79 +99,70 @@ impl Fsm for NodeGraphToolFsmState { type ToolData = NodeGraphToolData; type ToolOptions = (); - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, - _tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use FrameToolMessage::*; - use NodeGraphToolFsmState::*; + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data; let shape_data = &mut tool_data.data; - if let ToolMessage::Frame(event) = event { - match (self, event) { - (_, DocumentIsDirty | SelectionChanged) => { - tool_data.path_outlines.clear_selected(responses); - tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); + let ToolMessage::Frame(event) = event else { + return self; + }; + match (self, event) { + (_, FrameToolMessage::DocumentIsDirty | FrameToolMessage::SelectionChanged) => { + tool_data.path_outlines.clear_selected(responses); + tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); - self - } - (Ready, DragStart) => { - tool_data.path_outlines.clear_selected(responses); - - shape_data.start(responses, document, input, render_data); - responses.add(DocumentMessage::StartTransaction); - shape_data.path = Some(document.get_path_for_new_layer()); - responses.add(DocumentMessage::DeselectAllLayers); - - let network = node_graph::new_image_network(8, 0); - - responses.add(Operation::AddFrame { - path: shape_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - network, - }); - - Drawing - } - (state, Resize { center, lock_ratio }) => { - let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true); - responses.try_add(message); - - state - } - (Drawing, DragStop) => { - if let Some(layer_path) = &shape_data.path { - responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() }); - } - - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - Ready - } - (Drawing, Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - shape_data.cleanup(responses); - tool_data.path_outlines.clear_selected(responses); - - Ready - } - (_, Abort) => { - tool_data.path_outlines.clear_selected(responses); - - Ready - } - _ => self, + self } - } else { - self + (NodeGraphToolFsmState::Ready, FrameToolMessage::DragStart) => { + tool_data.path_outlines.clear_selected(responses); + + shape_data.start(responses, document, input, render_data); + responses.add(DocumentMessage::StartTransaction); + shape_data.path = Some(document.get_path_for_new_layer()); + responses.add(DocumentMessage::DeselectAllLayers); + + let network = node_graph::new_image_network(8, 0); + + responses.add(Operation::AddFrame { + path: shape_data.path.clone().unwrap(), + insert_index: -1, + transform: DAffine2::ZERO.to_cols_array(), + network, + }); + + NodeGraphToolFsmState::Drawing + } + (state, FrameToolMessage::Resize { center, lock_ratio }) => { + let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true); + responses.try_add(message); + + state + } + (NodeGraphToolFsmState::Drawing, FrameToolMessage::DragStop) => { + if let Some(layer_path) = &shape_data.path { + responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() }); + } + + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + shape_data.cleanup(responses); + + NodeGraphToolFsmState::Ready + } + (NodeGraphToolFsmState::Drawing, FrameToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + + shape_data.cleanup(responses); + tool_data.path_outlines.clear_selected(responses); + + NodeGraphToolFsmState::Ready + } + (_, FrameToolMessage::Abort) => { + tool_data.path_outlines.clear_selected(responses); + + NodeGraphToolFsmState::Ready + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 0c5475b558..7124416e84 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -1,14 +1,8 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; +use super::tool_prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::LayerId; -use document_legacy::Operation; use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::Color; @@ -123,33 +117,31 @@ impl LayoutHolder for FreehandTool { 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 { - FreehandOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - FreehandOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - FreehandOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else{ + self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); return; + }; + match action { + FreehandOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + FreehandOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + FreehandOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -191,74 +183,65 @@ impl Fsm for FreehandToolFsmState { type ToolData = FreehandToolData; type ToolOptions = FreehandOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use FreehandToolFsmState::*; - use FreehandToolMessage::*; + } = tool_action_data; - let transform = document.document_legacy.root.transform; + let transform = document.document_legacy.metadata.document_to_viewport; - if let ToolMessage::Freehand(event) = event { - match (self, event) { - (Ready, DragStart) => { - responses.add(DocumentMessage::StartTransaction); - responses.add(DocumentMessage::DeselectAllLayers); - tool_data.path = Some(document.get_path_for_new_layer()); + let ToolMessage::Freehand(event) = event else { + return self; + }; + match (self, event) { + (FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => { + responses.add(DocumentMessage::StartTransaction); + responses.add(DocumentMessage::DeselectAllLayers); + tool_data.path = Some(document.get_path_for_new_layer()); - let pos = transform.inverse().transform_point2(input.mouse.position); + let pos = transform.inverse().transform_point2(input.mouse.position); - tool_data.points.push(pos); + tool_data.points.push(pos); - tool_data.weight = tool_options.line_weight; + tool_data.weight = tool_options.line_weight; - add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); + add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); - Drawing - } - (Drawing, PointerMove) => { - let pos = transform.inverse().transform_point2(input.mouse.position); - - if tool_data.points.last() != Some(&pos) { - tool_data.points.push(pos); - } - - add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); - - Drawing - } - (Drawing, DragStop) | (Drawing, Abort) => { - if tool_data.points.len() >= 2 { - responses.add(remove_preview(tool_data)); - add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); - responses.add(DocumentMessage::CommitTransaction); - } else { - responses.add(DocumentMessage::AbortTransaction); - } - - tool_data.path = None; - tool_data.points.clear(); - - Ready - } - (_, FreehandToolMessage::WorkingColorChanged) => { - responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + FreehandToolFsmState::Drawing } - } else { - self + (FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => { + let pos = transform.inverse().transform_point2(input.mouse.position); + + if tool_data.points.last() != Some(&pos) { + tool_data.points.push(pos); + } + + add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); + + FreehandToolFsmState::Drawing + } + (FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop | FreehandToolMessage::Abort) => { + if tool_data.points.len() >= 2 { + responses.add(remove_preview(tool_data)); + add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses); + responses.add(DocumentMessage::CommitTransaction); + } else { + responses.add(DocumentMessage::AbortTransaction); + } + + tool_data.path = None; + tool_data.points.clear(); + + FreehandToolFsmState::Ready + } + (_, FreehandToolMessage::WorkingColorChanged) => { + responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } @@ -277,7 +260,7 @@ impl Fsm for FreehandToolFsmState { } fn remove_preview(data: &FreehandToolData) -> Message { - Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() + GraphOperationMessage::DeleteLayer { id: data.path.clone().unwrap()[0] }.into() } fn add_polyline(data: &FreehandToolData, stroke_color: Option, fill_color: Option, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 71d2b68ddd..1b127b47a0 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -1,12 +1,7 @@ +use super::tool_prelude::*; use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; 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::intersection::Quad; use document_legacy::layers::layer_info::Layer; @@ -16,9 +11,6 @@ use document_legacy::LayerId; use document_legacy::Operation; use graphene_core::raster::color::Color; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct GradientTool { fsm_state: GradientToolFsmState, @@ -26,16 +18,11 @@ pub struct GradientTool { options: GradientOptions, } +#[derive(Default)] pub struct GradientOptions { gradient_type: GradientType, } -impl Default for GradientOptions { - fn default() -> Self { - Self { gradient_type: GradientType::Linear } - } -} - #[remain::sorted] #[impl_message(Message, ToolMessage, Gradient)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] @@ -77,20 +64,19 @@ impl ToolMetadata for GradientTool { 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) => { - self.options.gradient_type = gradient_type; - if let Some(selected_gradient) = &mut self.data.selected_gradient { - selected_gradient.gradient.gradient_type = gradient_type; - selected_gradient.render_gradient(responses); - } + let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false); + return; + }; + match action { + GradientOptionsUpdate::Type(gradient_type) => { + self.options.gradient_type = gradient_type; + if let Some(selected_gradient) = &mut self.data.selected_gradient { + selected_gradient.gradient.gradient_type = gradient_type; + selected_gradient.render_gradient(responses); } } - return; } - - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false); } advertise_actions!(GradientToolMessageDiscriminant; @@ -394,253 +380,248 @@ impl Fsm for GradientToolFsmState { type ToolData = GradientToolData; type ToolOptions = GradientOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, render_data, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - if let ToolMessage::Gradient(event) = event { - match (self, event) { - (_, GradientToolMessage::DocumentIsDirty) => { - while let Some(overlay) = tool_data.gradient_overlays.pop() { - overlay.delete_overlays(responses); - } + } = tool_action_data; - if self != GradientToolFsmState::Drawing { - SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses); - } + let ToolMessage::Gradient(event) = event else { + return self; + }; - for path in document.selected_visible_layers() { - let layer = document.document_legacy.layer(path).unwrap(); - - if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) { - let dragging = tool_data - .selected_gradient - .as_ref() - .and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None }); - tool_data - .gradient_overlays - .push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data)) - } - } - - self + match (self, event) { + (_, GradientToolMessage::DocumentIsDirty) => { + while let Some(overlay) = tool_data.gradient_overlays.pop() { + overlay.delete_overlays(responses); } - (GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => { - let Some(selected_gradient) = &mut tool_data.selected_gradient else { - return self; - }; - // Skip if invalid gradient - if selected_gradient.gradient.positions.len() < 2 { - return self; - } - - // Remove the selected point - match selected_gradient.dragging { - GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0), - GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(), - GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index), - }; - - // 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 layer = selected_gradient.path.clone(); - responses.add(GraphOperationMessage::FillSet { layer, fill }); - return self; - } - - // Find the minimum and maximum positions - let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min"); - let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max"); - - // Recompute the start and end posiiton of the gradient (in viewport transform) - let transform = selected_gradient.transform; - let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end)); - let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position)); - selected_gradient.gradient.start = transform.inverse().transform_point2(new_start); - selected_gradient.gradient.end = transform.inverse().transform_point2(new_end); - - // Remap the positions - for (position, _) in selected_gradient.gradient.positions.iter_mut() { - *position = (*position - min_position) / (max_position - min_position); - } - - // Render the new gradient - selected_gradient.render_gradient(responses); - - self + if self != GradientToolFsmState::Drawing { + SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses); } - (_, GradientToolMessage::InsertStop) => { - for overlay in &tool_data.gradient_overlays { - let mouse = input.mouse.position; - let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end()); - // Compute the distance from the mouse to the gradient line in viewport space - let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length(); + for path in document.selected_visible_layers() { + let layer = document.document_legacy.layer(path).unwrap(); - // If click is on the line then insert point - if distance < SELECTION_THRESHOLD { - let mut gradient = overlay.gradient.clone(); - - // Try and insert the new stop - if let Some(index) = gradient.insert_stop(mouse, overlay.transform) { - document.backup_nonmut(responses); - - let layer = document.document_legacy.layer(&overlay.path); - if let Ok(layer) = layer { - let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data); - - // Select the new point - selected_gradient.dragging = GradientDragTarget::Step(index); - - // Update the layer fill - selected_gradient.render_gradient(responses); - - tool_data.selected_gradient = Some(selected_gradient); - } - - break; - } - } + if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) { + let dragging = tool_data + .selected_gradient + .as_ref() + .and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None }); + tool_data + .gradient_overlays + .push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data)) } - - self } - (GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => { - responses.add(BroadcastEvent::DocumentIsDirty); + self + } + (GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => { + let Some(selected_gradient) = &mut tool_data.selected_gradient else { + return self; + }; + + // Skip if invalid gradient + if selected_gradient.gradient.positions.len() < 2 { + return self; + } + + // Remove the selected point + match selected_gradient.dragging { + GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0), + GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(), + GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index), + }; + + // 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 layer = selected_gradient.path.clone(); + responses.add(GraphOperationMessage::FillSet { layer, fill }); + return self; + } + + // Find the minimum and maximum positions + let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min"); + let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max"); + + // Recompute the start and end posiiton of the gradient (in viewport transform) + let transform = selected_gradient.transform; + let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end)); + let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position)); + selected_gradient.gradient.start = transform.inverse().transform_point2(new_start); + selected_gradient.gradient.end = transform.inverse().transform_point2(new_end); + + // Remap the positions + for (position, _) in selected_gradient.gradient.positions.iter_mut() { + *position = (*position - min_position) / (max_position - min_position); + } + + // Render the new gradient + selected_gradient.render_gradient(responses); + + self + } + (_, GradientToolMessage::InsertStop) => { + for overlay in &tool_data.gradient_overlays { let mouse = input.mouse.position; - tool_data.drag_start = mouse; - let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2); + let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end()); - let mut dragging = false; - for overlay in &tool_data.gradient_overlays { - // Check for dragging step - for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() { - let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos)); - if pos.distance_squared(mouse) < tolerance { - dragging = true; - tool_data.selected_gradient = Some(SelectedGradient { - path: overlay.path.clone(), - transform: overlay.transform, - gradient: overlay.gradient.clone(), - dragging: GradientDragTarget::Step(index), - }) - } - } + // Compute the distance from the mouse to the gradient line in viewport space + let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length(); - // Check dragging start or end handle - for (pos, dragging_target) in [ - (overlay.evaluate_gradient_start(), GradientDragTarget::Start), - (overlay.evaluate_gradient_end(), GradientDragTarget::End), - ] { - if pos.distance_squared(mouse) < tolerance { - dragging = true; - start_snap(&mut tool_data.snap_manager, document, input, render_data); - tool_data.selected_gradient = Some(SelectedGradient { - path: overlay.path.clone(), - transform: overlay.transform, - gradient: overlay.gradient.clone(), - dragging: dragging_target, - }) + // If click is on the line then insert point + if distance < SELECTION_THRESHOLD { + let mut gradient = overlay.gradient.clone(); + + // Try and insert the new stop + if let Some(index) = gradient.insert_stop(mouse, overlay.transform) { + document.backup_nonmut(responses); + + let layer = document.document_legacy.layer(&overlay.path); + if let Ok(layer) = layer { + let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data); + + // Select the new point + selected_gradient.dragging = GradientDragTarget::Step(index); + + // Update the layer fill + selected_gradient.render_gradient(responses); + + tool_data.selected_gradient = Some(selected_gradient); } + + break; } } - if dragging { - document.backup_nonmut(responses); + } + + self + } + (GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => { + responses.add(BroadcastEvent::DocumentIsDirty); + + let mouse = input.mouse.position; + tool_data.drag_start = mouse; + let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2); + + let mut dragging = false; + for overlay in &tool_data.gradient_overlays { + // Check for dragging step + for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() { + let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos)); + if pos.distance_squared(mouse) < tolerance { + dragging = true; + tool_data.selected_gradient = Some(SelectedGradient { + path: overlay.path.clone(), + transform: overlay.transform, + gradient: overlay.gradient.clone(), + dragging: GradientDragTarget::Step(index), + }) + } + } + + // Check dragging start or end handle + for (pos, dragging_target) in [ + (overlay.evaluate_gradient_start(), GradientDragTarget::Start), + (overlay.evaluate_gradient_end(), GradientDragTarget::End), + ] { + if pos.distance_squared(mouse) < tolerance { + dragging = true; + start_snap(&mut tool_data.snap_manager, document, input, render_data); + tool_data.selected_gradient = Some(SelectedGradient { + path: overlay.path.clone(), + transform: overlay.transform, + gradient: overlay.gradient.clone(), + dragging: dragging_target, + }) + } + } + } + if dragging { + document.backup_nonmut(responses); + GradientToolFsmState::Drawing + } else { + let tolerance = DVec2::splat(SELECTION_TOLERANCE); + let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); + let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop(); + + // the intersection is the layer where the gradient is being applied + if let Some(intersection) = intersection { + let is_bitmap = document + .document_legacy + .layer(&intersection) + .ok() + .and_then(|layer| layer.as_layer().ok()) + .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_))); + if is_bitmap { + return self; + } + + if !document.selected_layers_contains(&intersection) { + let replacement_selected_layers = vec![intersection.clone()]; + + responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); + } + + let layer = document.document_legacy.layer(&intersection).unwrap(); + + responses.add(DocumentMessage::StartTransaction); + + // Use the already existing gradient if it exists + let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) { + gradient.clone() + } else { + // Generate a new gradient + Gradient::new( + DVec2::ZERO, + global_tool_data.secondary_color, + DVec2::ONE, + global_tool_data.primary_color, + DAffine2::IDENTITY, + generate_uuid(), + tool_options.gradient_type, + ) + }; + let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position); + + tool_data.selected_gradient = Some(selected_gradient); + + start_snap(&mut tool_data.snap_manager, document, input, render_data); + GradientToolFsmState::Drawing } else { - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); - let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop(); - - // the intersection is the layer where the gradient is being applied - if let Some(intersection) = intersection { - let is_bitmap = document - .document_legacy - .layer(&intersection) - .ok() - .and_then(|layer| layer.as_layer().ok()) - .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_))); - if is_bitmap { - return self; - } - - if !document.selected_layers_contains(&intersection) { - let replacement_selected_layers = vec![intersection.clone()]; - - responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); - } - - let layer = document.document_legacy.layer(&intersection).unwrap(); - - responses.add(DocumentMessage::StartTransaction); - - // Use the already existing gradient if it exists - let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) { - gradient.clone() - } else { - // Generate a new gradient - Gradient::new( - DVec2::ZERO, - global_tool_data.secondary_color, - DVec2::ONE, - global_tool_data.primary_color, - DAffine2::IDENTITY, - generate_uuid(), - tool_options.gradient_type, - ) - }; - let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position); - - tool_data.selected_gradient = Some(selected_gradient); - - start_snap(&mut tool_data.snap_manager, document, input, render_data); - - GradientToolFsmState::Drawing - } else { - GradientToolFsmState::Ready - } + GradientToolFsmState::Ready } } - (GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => { - if let Some(selected_gradient) = &mut tool_data.selected_gradient { - let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type); - } - GradientToolFsmState::Drawing - } - - (GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => { - input.mouse.finish_transaction(tool_data.drag_start, responses); - tool_data.snap_manager.cleanup(responses); - - GradientToolFsmState::Ready - } - - (_, GradientToolMessage::Abort) => { - tool_data.snap_manager.cleanup(responses); - - while let Some(overlay) = tool_data.gradient_overlays.pop() { - overlay.delete_overlays(responses); - } - GradientToolFsmState::Ready - } - _ => self, } - } else { - self + (GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => { + if let Some(selected_gradient) = &mut tool_data.selected_gradient { + let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type); + } + GradientToolFsmState::Drawing + } + + (GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => { + input.mouse.finish_transaction(tool_data.drag_start, responses); + tool_data.snap_manager.cleanup(responses); + + GradientToolFsmState::Ready + } + + (_, GradientToolMessage::Abort) => { + tool_data.snap_manager.cleanup(responses); + + while let Some(overlay) = tool_data.gradient_overlays.pop() { + overlay.delete_overlays(responses); + } + GradientToolFsmState::Ready + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index 0179e1fa89..4b98c1ba6e 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -1,12 +1,7 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; +use super::tool_prelude::*; use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE}; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::path_outline::PathOutline; 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::Operation; @@ -115,102 +110,98 @@ impl Fsm for ImaginateToolFsmState { _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - use ImaginateToolFsmState::*; - use ImaginateToolMessage::*; - let shape_data = &mut tool_data.data; - if let ToolMessage::Imaginate(event) = event { - match (self, event) { - (_, DocumentIsDirty | SelectionChanged) => { - tool_data.path_outlines.clear_selected(responses); - tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); + let ToolMessage::Imaginate(event) = event else{ + return self; + }; + match (self, event) { + (_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => { + tool_data.path_outlines.clear_selected(responses); + tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); - self - } - (Ready, DragStart) => { - tool_data.path_outlines.clear_selected(responses); - - shape_data.start(responses, document, input, render_data); - responses.add(DocumentMessage::StartTransaction); - shape_data.path = Some(document.get_path_for_new_layer()); - responses.add(DocumentMessage::DeselectAllLayers); - - use graph_craft::document::*; - - // Utility function to offset the position of each consecutive node - let mut pos = 8; - let mut next_pos = || { - pos += 8; - graph_craft::document::DocumentNodeMetadata::position((pos, 4)) - }; - - // Get the node type for the Transform and Imaginate nodes - let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else { - warn!("Transform node should be in registry"); - return Drawing; - }; - let imaginate_node_type = &*IMAGINATE_NODE; - - // Give them a unique ID - let [transform_node_id, imaginate_node_id] = [100, 101]; - - // Create the network based on the Input -> Output passthrough default network - let mut network = node_graph::new_image_network(16, imaginate_node_id); - - // Insert the nodes into the default network - network - .nodes - .insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos())); - network.nodes.insert( - imaginate_node_id, - imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()), - ); - - // Add a layer with a frame to the document - responses.add(Operation::AddFrame { - path: shape_data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - network, - }); - responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id }); - - Drawing - } - (state, Resize { center, lock_ratio }) => { - let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true); - responses.try_add(message); - - state - } - (Drawing, DragStop) => { - if let Some(layer_path) = &shape_data.path { - responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() }); - } - - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - Ready - } - (Drawing, Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - shape_data.cleanup(responses); - tool_data.path_outlines.clear_selected(responses); - - Ready - } - (_, Abort) => { - tool_data.path_outlines.clear_selected(responses); - - Ready - } - _ => self, + self } - } else { - self + (ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => { + tool_data.path_outlines.clear_selected(responses); + + shape_data.start(responses, document, input, render_data); + responses.add(DocumentMessage::StartTransaction); + shape_data.path = Some(document.get_path_for_new_layer()); + responses.add(DocumentMessage::DeselectAllLayers); + + use graph_craft::document::*; + + // Utility function to offset the position of each consecutive node + let mut pos = 8; + let mut next_pos = || { + pos += 8; + graph_craft::document::DocumentNodeMetadata::position((pos, 4)) + }; + + // Get the node type for the Transform and Imaginate nodes + let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else { + warn!("Transform node should be in registry"); + return ImaginateToolFsmState::Drawing; + }; + let imaginate_node_type = &*IMAGINATE_NODE; + + // Give them a unique ID + let [transform_node_id, imaginate_node_id] = [100, 101]; + + // Create the network based on the Input -> Output passthrough default network + let mut network = node_graph::new_image_network(16, imaginate_node_id); + + // Insert the nodes into the default network + network + .nodes + .insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos())); + network.nodes.insert( + imaginate_node_id, + imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()), + ); + + // Add a layer with a frame to the document + responses.add(Operation::AddFrame { + path: shape_data.path.clone().unwrap(), + insert_index: -1, + transform: DAffine2::ZERO.to_cols_array(), + network, + }); + responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id }); + + ImaginateToolFsmState::Drawing + } + (state, ImaginateToolMessage::Resize { center, lock_ratio }) => { + let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true); + responses.try_add(message); + + state + } + (ImaginateToolFsmState::Drawing, ImaginateToolMessage::DragStop) => { + if let Some(layer_path) = &shape_data.path { + responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() }); + } + + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + shape_data.cleanup(responses); + + ImaginateToolFsmState::Ready + } + (ImaginateToolFsmState::Drawing, ImaginateToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + + shape_data.cleanup(responses); + tool_data.path_outlines.clear_selected(responses); + + ImaginateToolFsmState::Ready + } + (_, ImaginateToolMessage::Abort) => { + tool_data.path_outlines.clear_selected(responses); + + ImaginateToolFsmState::Ready + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index dbf1249e76..b4444e1301 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -1,22 +1,13 @@ +use super::tool_prelude::*; use crate::consts::LINE_ROTATE_SNAP_ANGLE; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::LayerId; use graphene_core::vector::style::Stroke; use graphene_core::Color; -use glam::DVec2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct LineTool { fsm_state: LineToolFsmState, @@ -112,26 +103,24 @@ impl LayoutHolder for LineTool { impl<'a> MessageHandler> for LineTool { fn process_message(&mut self, message: ToolMessage, responses: &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, - LineOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - LineOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + match action { + LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + LineOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + LineOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -162,8 +151,8 @@ enum LineToolFsmState { #[derive(Clone, Debug, Default)] struct LineToolData { - drag_start: ViewportPosition, - drag_current: ViewportPosition, + drag_start: DVec2, + drag_current: DVec2, angle: f64, weight: f64, path: Option>, @@ -174,76 +163,70 @@ impl Fsm for LineToolFsmState { type ToolData = LineToolData; type ToolOptions = LineOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, render_data, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use LineToolFsmState::*; - use LineToolMessage::*; + } = tool_action_data; - if let ToolMessage::Line(event) = event { - match (self, event) { - (Ready, DragStart) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - 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 ToolMessage::Line(event) = event else { + return self; + }; + match (self, event) { + (LineToolFsmState::Ready, LineToolMessage::DragStart) => { + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X); + let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + tool_data.drag_start = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(viewport_start); - responses.add(DocumentMessage::StartTransaction); - 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, - stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), - }); + let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X); - tool_data.weight = tool_options.line_weight; + responses.add(DocumentMessage::StartTransaction); + 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, + stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), + }); - Drawing - } - (Drawing, Redraw { center, snap_angle, lock_angle }) => { - tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + tool_data.weight = tool_options.line_weight; - let keyboard = &input.keyboard; - responses.add(generate_transform(tool_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center))); - - Drawing - } - (Drawing, DragStop) => { - tool_data.snap_manager.cleanup(responses); - input.mouse.finish_transaction(tool_data.drag_start, responses); - tool_data.path = None; - Ready - } - (Drawing, Abort) => { - tool_data.snap_manager.cleanup(responses); - responses.add(DocumentMessage::AbortTransaction); - tool_data.path = None; - Ready - } - (_, WorkingColorChanged) => { - responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + LineToolFsmState::Drawing } - } else { - self + (LineToolFsmState::Drawing, LineToolMessage::Redraw { center, snap_angle, lock_angle }) => { + tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + + let keyboard = &input.keyboard; + let transform = document.document_legacy.metadata.document_to_viewport; + responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center))); + + LineToolFsmState::Drawing + } + (LineToolFsmState::Drawing, LineToolMessage::DragStop) => { + tool_data.snap_manager.cleanup(responses); + input.mouse.finish_transaction(tool_data.drag_start, responses); + tool_data.path = None; + LineToolFsmState::Ready + } + (LineToolFsmState::Drawing, LineToolMessage::Abort) => { + tool_data.snap_manager.cleanup(responses); + responses.add(DocumentMessage::AbortTransaction); + tool_data.path = None; + LineToolFsmState::Ready + } + (_, LineToolMessage::WorkingColorChanged) => { + responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } @@ -270,8 +253,8 @@ impl Fsm for LineToolFsmState { } } -fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle: bool, center: bool) -> Message { - let mut start = tool_data.drag_start; +fn generate_transform(tool_data: &mut LineToolData, document_to_viewport: DAffine2, lock_angle: bool, snap_angle: bool, center: bool) -> Message { + let mut start = document_to_viewport.transform_point2(tool_data.drag_start); let line_vector = tool_data.drag_current - start; let mut angle = -line_vector.angle_between(DVec2::X); diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index 8f2b31110a..9e06d4ae96 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -16,3 +16,15 @@ pub mod rectangle_tool; pub mod select_tool; pub mod spline_tool; pub mod text_tool; + +pub mod tool_prelude { + pub use crate::messages::frontend::utility_types::MouseCursorIcon; + pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; + pub use crate::messages::layout::utility_types::widget_prelude::*; + pub use crate::messages::prelude::*; + pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; + pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; + + pub use glam::{DAffine2, DVec2}; + pub use serde::{Deserialize, Serialize}; +} diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 09f7d1dc67..481782c0e6 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,12 +1,4 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; -use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; - -use glam::DVec2; -use serde::{Deserialize, Serialize}; +use super::tool_prelude::*; #[derive(Default)] pub struct NavigateTool { @@ -112,59 +104,57 @@ impl Fsm for NavigateToolFsmState { _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - if let ToolMessage::Navigate(navigate) = message { - use NavigateToolMessage::*; + let ToolMessage::Navigate(navigate) = message else{ + return self; + }; - match navigate { - ClickZoom { zoom_in } => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); + match navigate { + NavigateToolMessage::ClickZoom { zoom_in } => { + responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); - // Mouse has not moved from pointerdown to pointerup - if tool_data.drag_start == input.mouse.position { - responses.add_front(if zoom_in { - NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true } - } else { - NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true } - }); - } - - NavigateToolFsmState::Ready - } - PointerMove { snap_angle, snap_zoom } => { - responses.add_front(NavigationMessage::PointerMove { - snap_angle, - wait_for_snap_angle_release: false, - snap_zoom, - zoom_from_viewport: Some(tool_data.drag_start), + // Mouse has not moved from pointerdown to pointerup + if tool_data.drag_start == input.mouse.position { + responses.add_front(if zoom_in { + NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true } + } else { + NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true } }); - self - } - TranslateCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::TranslateCanvasBegin); - NavigateToolFsmState::Panning - } - RotateCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }); - NavigateToolFsmState::Tilting - } - ZoomCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::ZoomCanvasBegin); - NavigateToolFsmState::Zooming - } - TransformCanvasEnd => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); - NavigateToolFsmState::Ready - } - Abort => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); - NavigateToolFsmState::Ready } + + NavigateToolFsmState::Ready + } + NavigateToolMessage::PointerMove { snap_angle, snap_zoom } => { + responses.add_front(NavigationMessage::PointerMove { + snap_angle, + wait_for_snap_angle_release: false, + snap_zoom, + zoom_from_viewport: Some(tool_data.drag_start), + }); + self + } + NavigateToolMessage::TranslateCanvasBegin => { + tool_data.drag_start = input.mouse.position; + responses.add_front(NavigationMessage::TranslateCanvasBegin); + NavigateToolFsmState::Panning + } + NavigateToolMessage::RotateCanvasBegin => { + tool_data.drag_start = input.mouse.position; + responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }); + NavigateToolFsmState::Tilting + } + NavigateToolMessage::ZoomCanvasBegin => { + tool_data.drag_start = input.mouse.position; + responses.add_front(NavigationMessage::ZoomCanvasBegin); + NavigateToolFsmState::Zooming + } + NavigateToolMessage::TransformCanvasEnd => { + responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); + NavigateToolFsmState::Ready + } + NavigateToolMessage::Abort => { + responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); + NavigateToolFsmState::Ready } - } else { - self } } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 8ba48bd3f4..76651c5e84 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1,24 +1,18 @@ use std::vec; +use super::tool_prelude::*; use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, ShapeState}; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, transform_from_box}; -use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use document_legacy::document::Document; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::intersection::Quad; use document_legacy::{LayerId, Operation}; use graphene_core::vector::{ManipulatorPointId, SelectedType}; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct PathTool { fsm_state: PathToolFsmState, @@ -219,12 +213,13 @@ 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); + let layer = LayerNodeIdentifier::from_path(layer_path, document.network()); + shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, 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); + for &layer in shape_editor.selected_shape_state.keys() { + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); } self.opposing_handle_lengths = None; @@ -235,313 +230,313 @@ impl Fsm for PathToolFsmState { type ToolData = PathToolData; type ToolOptions = (); - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { + let 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 newly targeted layers to visible - let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); - shape_editor.set_selected_layers(layer_paths); + } = tool_action_data; + let ToolMessage::Path(event) = event else { + return self; + }; + match (self, event) { + (_, PathToolMessage::SelectionChanged) => { + // Set the newly targeted layers to visible + let layer_paths = document + .selected_visible_layers() + .map(|layer_path| LayerNodeIdentifier::from_path(layer_path, document.network())) + .collect(); + shape_editor.set_selected_layers(layer_paths); + + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + + responses.add(PathToolMessage::SelectedPointUpdated); + // This can happen in any state (which is why we return self) + self + } + (_, PathToolMessage::DocumentIsDirty) => { + // 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() { + let layer = LayerNodeIdentifier::from_path(layer_path, document.network()); + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); + } + + responses.add(PathToolMessage::SelectedPointUpdated); + + self + } + // Mouse down + (_, PathToolMessage::DragStart { add_to_selection }) => { + 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) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) { + responses.add(DocumentMessage::StartTransaction); + + //tool_data + // .snap_manager + // .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 additional_selected_points = Vec::new(); + for point in selected_points.points.iter() { + if point.point_id.manipulator_type == SelectedType::Anchor { + additional_selected_points.push(ManipulatorPointInfo { + layer: point.layer, + point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle), + }); + additional_selected_points.push(ManipulatorPointInfo { + layer: point.layer, + point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle), + }); + } + } + selected_points.points.extend(additional_selected_points); + + //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); responses.add(PathToolMessage::SelectedPointUpdated); - // This can happen in any state (which is why we return self) - self - } - (_, PathToolMessage::DocumentIsDirty) => { - // 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() { - shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); - } - - responses.add(PathToolMessage::SelectedPointUpdated); - - self - } - // Mouse down - (_, PathToolMessage::DragStart { add_to_selection }) => { - 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) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) { - responses.add(DocumentMessage::StartTransaction); - - tool_data - .snap_manager - .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 additional_selected_points = Vec::new(); - for point in selected_points.points.iter() { - 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), - }); - 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(additional_selected_points); - - 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); - - responses.add(PathToolMessage::SelectedPointUpdated); - PathToolFsmState::Dragging - } - // We didn't find a point nearby, so consider selecting the nearest shape instead - else { - let selection_size = DVec2::new(2.0, 2.0); - // Select shapes directly under our mouse - let intersection = document - .document_legacy - .intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data); - if !intersection.is_empty() { - if shift_pressed { - responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection }); - } else { - // Selects the topmost layer when selecting intersecting shapes - let top_most_intersection = intersection[intersection.len() - 1].clone(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![top_most_intersection.clone()], - }); - 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. - shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection); - return PathToolFsmState::Dragging; - } - } else { - // an empty intersection means that the user is drawing a box - tool_data.drag_start_pos = input.mouse.position; - tool_data.previous_mouse_position = input.mouse.position; - - tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses)); - return PathToolFsmState::DrawingBox; - } - - PathToolFsmState::Ready - } - } - (PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => { - tool_data.previous_mouse_position = input.mouse.position; - - responses.add_front(DocumentMessage::Overlays( - Operation::SetLayerTransformInViewport { - path: tool_data.drag_box_overlay_layer.clone().unwrap(), - transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(), - } - .into(), - )); - PathToolFsmState::DrawingBox - } - - // Dragging - ( - PathToolFsmState::Dragging, - PathToolMessage::PointerMove { - alt_mirror_angle, - shift_mirror_distance, - }, - ) => { - // Determine when alt state changes - let alt_pressed = input.keyboard.get(alt_mirror_angle as usize); - - // 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(shape_editor.opposing_handle_lengths(&document.document_legacy)); - } - } else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths { - 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); - 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 } - - (PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => { - let shift_pressed = input.keyboard.get(add_to_selection as usize); - - if tool_data.drag_start_pos == tool_data.previous_mouse_position { - responses.add(DocumentMessage::DeselectAllLayers); + // We didn't find a point nearby, so consider selecting the nearest shape instead + else { + let selection_size = DVec2::new(2.0, 2.0); + // Select shapes directly under our mouse + let intersection = document + .document_legacy + .intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data); + if !intersection.is_empty() { + if shift_pressed { + responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection }); + } else { + // Selects the topmost layer when selecting intersecting shapes + let top_most_intersection = intersection[intersection.len() - 1].clone(); + responses.add(DocumentMessage::SetSelectedLayers { + replacement_selected_layers: vec![top_most_intersection.clone()], + }); + 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. + let layer = LayerNodeIdentifier::from_path(&top_most_intersection, document.network()); + shape_editor.select_all_anchors(&document.document_legacy, layer); + return PathToolFsmState::Dragging; + } } else { - shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed); - tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); - }; + // an empty intersection means that the user is drawing a box + tool_data.drag_start_pos = input.mouse.position; + tool_data.previous_mouse_position = input.mouse.position; - responses.add_front(DocumentMessage::Overlays( - Operation::DeleteLayer { - path: tool_data.drag_box_overlay_layer.take().unwrap(), - } - .into(), - )); - - responses.add(PathToolMessage::SelectedPointUpdated); - PathToolFsmState::Ready - } - - // Mouse up - (PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => { - let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); - - if tool_data.drag_start_pos == tool_data.previous_mouse_position { - responses.add(DocumentMessage::DeselectAllLayers); - } else { - shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed); - tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); - }; - - responses.add_front(DocumentMessage::Overlays( - Operation::DeleteLayer { - path: tool_data.drag_box_overlay_layer.take().unwrap(), - } - .into(), - )); - - responses.add(PathToolMessage::SelectedPointUpdated); - PathToolFsmState::Ready - } - (_, PathToolMessage::DragStop { shift_mirror_distance }) => { - let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); - - let nearest_point = shape_editor - .find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD) - .map(|(_, nearest_point)| nearest_point); - - shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses); - - if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed { - 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); - tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); - } + tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses)); + return PathToolFsmState::DrawingBox; } - responses.add(PathToolMessage::SelectedPointUpdated); - tool_data.snap_manager.cleanup(responses); PathToolFsmState::Ready } - - // Delete key - (_, PathToolMessage::Delete) => { - // Delete the selected points and clean up overlays - responses.add(DocumentMessage::StartTransaction); - shape_editor.delete_selected_points(responses); - responses.add(PathToolMessage::SelectionChanged); - for layer_path in document.all_layers() { - 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 !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 - shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses); - } - - responses.add(PathToolMessage::SelectedPointUpdated); - self - } - (_, PathToolMessage::Abort) => { - // TODO Tell overlay manager to remove the overlays - for layer_path in document.all_layers() { - shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } - PathToolFsmState::Ready - } - ( - _, - PathToolMessage::PointerMove { - alt_mirror_angle: _, - shift_mirror_distance: _, - }, - ) => self, - (_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => { - shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses); - PathToolFsmState::Ready - } - (_, PathToolMessage::SelectAllPoints) => { - shape_editor.select_all_points(&document.document_legacy); - tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); - PathToolFsmState::Ready - } - (_, PathToolMessage::SelectedPointXChanged { new_x }) => { - if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() { - shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path); - } - PathToolFsmState::Ready - } - (_, PathToolMessage::SelectedPointYChanged { new_y }) => { - if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() { - shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path); - } - PathToolFsmState::Ready - } - (_, PathToolMessage::SelectedPointUpdated) => { - tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor); - self - } - (_, PathToolMessage::ManipulatorAngleMakeSmooth) => { - responses.add(DocumentMessage::StartTransaction); - shape_editor.set_handle_mirroring_on_selected(true, responses); - shape_editor.smooth_selected_groups(responses, &document.document_legacy); - responses.add(DocumentMessage::CommitTransaction); - PathToolFsmState::Ready - } - (_, PathToolMessage::ManipulatorAngleMakeSharp) => { - responses.add(DocumentMessage::StartTransaction); - shape_editor.set_handle_mirroring_on_selected(false, responses); - responses.add(DocumentMessage::CommitTransaction); - PathToolFsmState::Ready - } - (_, _) => PathToolFsmState::Ready, } - } else { - self + (PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => { + tool_data.previous_mouse_position = input.mouse.position; + + responses.add_front(DocumentMessage::Overlays( + Operation::SetLayerTransformInViewport { + path: tool_data.drag_box_overlay_layer.clone().unwrap(), + transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(), + } + .into(), + )); + PathToolFsmState::DrawingBox + } + + // Dragging + ( + PathToolFsmState::Dragging, + PathToolMessage::PointerMove { + alt_mirror_angle, + shift_mirror_distance, + }, + ) => { + // Determine when alt state changes + let alt_pressed = input.keyboard.get(alt_mirror_angle as usize); + + // 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(shape_editor.opposing_handle_lengths(&document.document_legacy)); + } + } else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths { + 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); + 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 + } + + (PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => { + let shift_pressed = input.keyboard.get(add_to_selection as usize); + + if tool_data.drag_start_pos == tool_data.previous_mouse_position { + responses.add(DocumentMessage::DeselectAllLayers); + } else { + shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed); + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + }; + + responses.add_front(DocumentMessage::Overlays( + Operation::DeleteLayer { + path: tool_data.drag_box_overlay_layer.take().unwrap(), + } + .into(), + )); + + responses.add(PathToolMessage::SelectedPointUpdated); + PathToolFsmState::Ready + } + + // Mouse up + (PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => { + let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); + + if tool_data.drag_start_pos == tool_data.previous_mouse_position { + responses.add(DocumentMessage::DeselectAllLayers); + } else { + shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed); + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + }; + + responses.add_front(DocumentMessage::Overlays( + Operation::DeleteLayer { + path: tool_data.drag_box_overlay_layer.take().unwrap(), + } + .into(), + )); + + responses.add(PathToolMessage::SelectedPointUpdated); + PathToolFsmState::Ready + } + (_, PathToolMessage::DragStop { shift_mirror_distance }) => { + let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); + + let nearest_point = shape_editor + .find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD) + .map(|(_, nearest_point)| nearest_point); + + shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses); + + if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed { + 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); + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + } + } + + responses.add(PathToolMessage::SelectedPointUpdated); + tool_data.snap_manager.cleanup(responses); + PathToolFsmState::Ready + } + + // Delete key + (_, PathToolMessage::Delete) => { + // Delete the selected points and clean up overlays + responses.add(DocumentMessage::StartTransaction); + shape_editor.delete_selected_points(responses); + responses.add(PathToolMessage::SelectionChanged); + for layer_path in document.all_layers() { + let layer = LayerNodeIdentifier::from_path(layer_path, document.network()); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses); + } + PathToolFsmState::Ready + } + (_, PathToolMessage::InsertPoint) => { + // First we try and flip the sharpness (if they have clicked on an anchor) + 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 + shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses); + } + + responses.add(PathToolMessage::SelectedPointUpdated); + self + } + (_, PathToolMessage::Abort) => { + // TODO Tell overlay manager to remove the overlays + for layer_path in document.all_layers() { + let layer = LayerNodeIdentifier::from_path(layer_path, document.network()); + shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses); + } + PathToolFsmState::Ready + } + ( + _, + PathToolMessage::PointerMove { + alt_mirror_angle: _, + shift_mirror_distance: _, + }, + ) => self, + (_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => { + shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses); + PathToolFsmState::Ready + } + (_, PathToolMessage::SelectAllPoints) => { + shape_editor.select_all_points(&document.document_legacy); + tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); + PathToolFsmState::Ready + } + (_, PathToolMessage::SelectedPointXChanged { new_x }) => { + if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() { + shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path); + } + PathToolFsmState::Ready + } + (_, PathToolMessage::SelectedPointYChanged { new_y }) => { + if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() { + shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path); + } + PathToolFsmState::Ready + } + (_, PathToolMessage::SelectedPointUpdated) => { + tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor); + self + } + (_, PathToolMessage::ManipulatorAngleMakeSmooth) => { + responses.add(DocumentMessage::StartTransaction); + shape_editor.set_handle_mirroring_on_selected(true, responses); + shape_editor.smooth_selected_groups(responses, &document.document_legacy); + responses.add(DocumentMessage::CommitTransaction); + PathToolFsmState::Ready + } + (_, PathToolMessage::ManipulatorAngleMakeSharp) => { + responses.add(DocumentMessage::StartTransaction); + shape_editor.set_handle_mirroring_on_selected(false, responses); + responses.add(DocumentMessage::CommitTransaction); + PathToolFsmState::Ready + } + (_, _) => PathToolFsmState::Ready, } } @@ -620,7 +615,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se // Check to see if only one manipulator group is selected let selection_layers: Vec<_> = shape_state.selected_shape_state.iter().take(2).map(|(k, v)| (k, v.selected_points_count())).collect(); if let [(layer, 1)] = selection_layers[..] { - let Some(layer_data) = document.layer(layer).ok() else { return SelectionStatus::None }; + let Some(layer_data) = document.layer(&layer.to_path()).ok() else { return SelectionStatus::None }; let Some(vector_data) = layer_data.as_vector_data() else { return SelectionStatus::None }; let Some(point) = shape_state.selected_points().next() else { return SelectionStatus::None; @@ -641,7 +636,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se return SelectionStatus::One(SingleSelectedPoint { coordinates: layer_data.transform.transform_point2(local_position) + layer_data.pivot, - layer_path: layer.clone(), + layer_path: layer.to_path(), id: *point, manipulator_angle, }); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index d4d05424fb..9cb3dbf139 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1,28 +1,22 @@ +use super::tool_prelude::*; use crate::consts::LINE_ROTATE_SNAP_ANGLE; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::VectorDataModification; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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 crate::messages::tool::tool_messages::pen_tool::graph_modification_utils::NodeGraphLayer; use bezier_rs::Subpath; +use document_legacy::document::Document; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; -use graph_craft::document::NodeInput; use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::vector::{ManipulatorPointId, SelectedType}; use graphene_core::Color; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct PenTool { fsm_state: PenToolFsmState, @@ -144,33 +138,31 @@ impl LayoutHolder for PenTool { 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, - PenOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - PenOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - PenOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + match action { + PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + PenOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + PenOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + PenOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -227,7 +219,7 @@ impl PenToolData { self.subpath_index = subpath_index; // Stop the handles on the first point from mirroring - let Some(subpaths) = get_subpaths(layer, document) else { return }; + let Some(subpaths) = get_subpaths (LayerNodeIdentifier::from_path(layer, document.network()), &document.document_legacy) else { return }; let manipulator_groups = subpaths[subpath_index].manipulator_groups(); let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { return; @@ -257,7 +249,7 @@ impl PenToolData { 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 transform = document.document_legacy.metadata.document_to_viewport * 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; @@ -286,7 +278,7 @@ impl PenToolData { 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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; + let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index]; // Get the last manipulator group and the one previous to that let mut manipulator_groups = subpath.manipulator_groups().iter(); @@ -327,7 +319,7 @@ impl PenToolData { // 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); + shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses); } self.should_mirror = false; @@ -337,7 +329,7 @@ impl PenToolData { 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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; + let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index]; // Get the last manipulator group and the one previous to that let mut manipulator_groups = subpath.manipulator_groups().iter(); @@ -394,7 +386,7 @@ impl PenToolData { // Clean up overlays for layer_path in document.all_layers() { - shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); + shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses); } // Clean up tool data @@ -415,7 +407,7 @@ impl PenToolData { 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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; + let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index]; // Get the last manipulator group let manipulator_groups = subpath.manipulator_groups(); @@ -468,7 +460,7 @@ impl PenToolData { 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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; + let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index]; // Get the last manipulator group and the one previous to that let mut manipulator_groups = subpath.manipulator_groups().iter(); @@ -513,7 +505,7 @@ impl PenToolData { fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque) -> Option { // Get subpath let layer_path = self.path.as_ref()?; - let subpath = &get_subpaths(layer_path, document)?[self.subpath_index]; + let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index]; // Abort if only one manipulator group has been placed if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 { @@ -560,11 +552,8 @@ impl Fsm for PenToolFsmState { type ToolData = PenToolData; type ToolOptions = PenOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, @@ -572,10 +561,8 @@ impl Fsm for PenToolFsmState { shape_editor, shape_overlay, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { + } = tool_action_data; + let mut transform = tool_data.path.as_ref().and_then(|path| document.document_legacy.multiply_transforms(path).ok()).unwrap_or_default(); if !transform.inverse().is_finite() { @@ -591,117 +578,118 @@ impl Fsm for PenToolFsmState { transform = DAffine2::IDENTITY; } - if let ToolMessage::Pen(event) = event { - match (self, event) { - (_, PenToolMessage::CanvasTransformed) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - self - } - (_, PenToolMessage::DocumentIsDirty) => { - // 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() { - 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() { - shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses); - } + transform = document.document_legacy.metadata.document_to_viewport * transform; - // Redraw the overlays of the newly selected layers - for layer_path in document.selected_visible_layers() { - shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses); - } - self - } - (_, PenToolMessage::WorkingColorChanged) => { - responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - (PenToolFsmState::Ready, PenToolMessage::DragStart) => { - responses.add(DocumentMessage::StartTransaction); - - // Initialize snapping - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - - // Disable this tool's mirroring - tool_data.should_mirror = false; - - // Perform extension of an existing path - 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, - tool_options.stroke.active_color(), - tool_options.fill.active_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) => { - tool_data.check_break(document, transform, shape_overlay, responses); - PenToolFsmState::DraggingHandle - } - (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => { - tool_data.should_mirror = true; - 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 modifiers = ModifierState { - snap_angle: input.keyboard.key(snap_angle), - lock_angle: input.keyboard.key(lock_angle), - break_handle: input.keyboard.key(break_handle), - }; - tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready) - } - (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), - }; - 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 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() { - shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } - tool_data.path = None; - tool_data.snap_manager.cleanup(responses); - - PenToolFsmState::Ready - } - (_, PenToolMessage::Abort) => { - // Clean up overlays - for layer_path in document.all_layers() { - shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses); - } - self - } - _ => self, + let ToolMessage::Pen(event) = event else { + return self; + }; + match (self, event) { + (_, PenToolMessage::CanvasTransformed) => { + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + self } - } else { - self + (_, PenToolMessage::DocumentIsDirty) => { + // 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 in document.document_legacy.metadata.selected_layers() { + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); + } + self + } + (_, PenToolMessage::SelectionChanged) => { + // Set the previously selected layers to invisible + for layer in document.document_legacy.metadata.all_layers() { + shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses); + } + + // Redraw the overlays of the newly selected layers + for layer in document.document_legacy.metadata.selected_layers() { + shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); + } + self + } + (_, PenToolMessage::WorkingColorChanged) => { + responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + (PenToolFsmState::Ready, PenToolMessage::DragStart) => { + responses.add(DocumentMessage::StartTransaction); + + // Initialize snapping + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + + // Disable this tool's mirroring + tool_data.should_mirror = false; + + // Perform extension of an existing path + 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, + tool_options.stroke.active_color(), + tool_options.fill.active_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) => { + tool_data.check_break(document, transform, shape_overlay, responses); + PenToolFsmState::DraggingHandle + } + (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => { + tool_data.should_mirror = true; + 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 modifiers = ModifierState { + snap_angle: input.keyboard.key(snap_angle), + lock_angle: input.keyboard.key(lock_angle), + break_handle: input.keyboard.key(break_handle), + }; + tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready) + } + (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), + }; + 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 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() { + shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses); + } + tool_data.path = None; + tool_data.snap_manager.cleanup(responses); + + PenToolFsmState::Ready + } + (_, PenToolMessage::Abort) => { + // Clean up overlays + for layer_path in document.all_layers() { + shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses); + } + self + } + _ => self, } } @@ -772,7 +760,7 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) continue; }; - let subpaths = get_subpaths(layer_path, document)?; + let subpaths = get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?; for (subpath_index, subpath) in subpaths.iter().enumerate() { if subpath.closed() { continue; @@ -794,22 +782,25 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) best } -fn get_subpaths<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler) -> Option<&'a Vec>> { - let layer = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_layer().ok())?; - let network = &layer.network; - for (node, _node_id) in network.primary_flow() { - if node.name == "Shape" { - let subpaths_input = node.inputs.get(0)?; - let NodeInput::Value { - tagged_value: TaggedValue::Subpaths(subpaths), - .. - } = subpaths_input - else { - continue; - }; - - return Some(subpaths); - } +pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec>> { + if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 0)? { + Some(subpaths) + } else { + None } - None +} + +pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec> { + if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? { + Some(mirror_handles) + } else { + None + } +} + +pub fn get_manipulator_groups(subpaths: &[Subpath]) -> impl Iterator> + DoubleEndedIterator { + subpaths.iter().flat_map(|subpath| subpath.manipulator_groups()) +} +pub fn get_manipulator_from_id(subpaths: &[Subpath], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup> { + subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id)) } diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index 6a8a74fa44..4c5578562e 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -1,19 +1,11 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; +use super::tool_prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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 graphene_core::vector::style::{Fill, Stroke}; use graphene_core::Color; -use glam::DVec2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct PolygonTool { fsm_state: PolygonToolFsmState, @@ -158,35 +150,33 @@ impl LayoutHolder for PolygonTool { } impl<'a> MessageHandler> for PolygonTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message { - match action { - PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, - PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type, - PolygonOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - PolygonOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - PolygonOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + match action { + PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, + PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type, + PolygonOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + PolygonOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + PolygonOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -232,87 +222,78 @@ impl Fsm for PolygonToolFsmState { type ToolData = PolygonToolData; type ToolOptions = PolygonOptions; - fn transition( - self, - event: ToolMessage, - tool_data: &mut Self::ToolData, - ToolActionHandlerData { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { + let ToolActionHandlerData { document, global_tool_data, input, render_data, .. - }: &mut ToolActionHandlerData, - tool_options: &Self::ToolOptions, - responses: &mut VecDeque, - ) -> Self { - use PolygonToolFsmState::*; - use PolygonToolMessage::*; + } = tool_action_data; let polygon_data = &mut tool_data.data; - if let ToolMessage::Polygon(event) = event { - match (self, event) { - (Drawing, CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); - self - } - (Ready, DragStart) => { - polygon_data.start(responses, document, input, render_data); - responses.add(DocumentMessage::StartTransaction); - let layer_path = document.get_path_for_new_layer(); - polygon_data.path = Some(layer_path.clone()); - - let subpath = match tool_options.primitive_shape_type { - PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.), - PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), - }; - graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); - - let fill_color = tool_options.fill.active_color(); - responses.add(GraphOperationMessage::FillSet { - layer: layer_path.clone(), - fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, - }); - - responses.add(GraphOperationMessage::StrokeSet { - layer: layer_path, - stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), - }); - - Drawing - } - (state, Resize { center, lock_ratio }) => { - if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) { - responses.add(message); - } - - state - } - (Drawing, DragStop) => { - input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses); - polygon_data.cleanup(responses); - - Ready - } - (Drawing, Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - polygon_data.cleanup(responses); - - Ready - } - (_, WorkingColorChanged) => { - responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + let ToolMessage::Polygon(event) = event else { + return self; + }; + match (self, event) { + (PolygonToolFsmState::Drawing, PolygonToolMessage::CanvasTransformed) => { + tool_data.data.recalculate_snaps(document, input, render_data); + self } - } else { - self + (PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => { + polygon_data.start(responses, document, input, render_data); + responses.add(DocumentMessage::StartTransaction); + let layer_path = document.get_path_for_new_layer(); + polygon_data.path = Some(layer_path.clone()); + + let subpath = match tool_options.primitive_shape_type { + PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.), + PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), + }; + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + + let fill_color = tool_options.fill.active_color(); + responses.add(GraphOperationMessage::FillSet { + layer: layer_path.clone(), + fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, + }); + + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path, + stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), + }); + + PolygonToolFsmState::Drawing + } + (state, PolygonToolMessage::Resize { center, lock_ratio }) => { + if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) { + responses.add(message); + } + + state + } + (PolygonToolFsmState::Drawing, PolygonToolMessage::DragStop) => { + input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses); + polygon_data.cleanup(responses); + + PolygonToolFsmState::Ready + } + (PolygonToolFsmState::Drawing, PolygonToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); + + polygon_data.cleanup(responses); + + PolygonToolFsmState::Ready + } + (_, PolygonToolMessage::WorkingColorChanged) => { + responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 3239d6a20a..341549b39c 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -1,17 +1,10 @@ -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; +use super::tool_prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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 glam::DVec2; use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::Color; -use serde::{Deserialize, Serialize}; #[derive(Default)] pub struct RectangleTool { @@ -107,33 +100,32 @@ impl LayoutHolder for RectangleTool { impl<'a> MessageHandler> for RectangleTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message { - match action { - RectangleOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, - RectangleOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - RectangleOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + + match action { + RectangleOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + RectangleOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + RectangleOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -210,66 +202,66 @@ impl Fsm for RectangleToolFsmState { let shape_data = &mut tool_data.data; - if let ToolMessage::Rectangle(event) = event { - match (self, event) { - (Drawing, CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); - self - } - (Ready, DragStart) => { - shape_data.start(responses, document, input, render_data); + let ToolMessage::Rectangle(event) = event else { + return self; + }; - let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE); - - let layer_path = document.get_path_for_new_layer(); - responses.add(DocumentMessage::StartTransaction); - shape_data.path = Some(layer_path.clone()); - graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); - - let fill_color = tool_options.fill.active_color(); - responses.add(GraphOperationMessage::FillSet { - layer: layer_path.clone(), - fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, - }); - - responses.add(GraphOperationMessage::StrokeSet { - layer: layer_path, - stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), - }); - - Drawing - } - (state, Resize { center, lock_ratio }) => { - if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) { - responses.add(message); - } - - state - } - (Drawing, DragStop) => { - input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); - shape_data.cleanup(responses); - - Ready - } - (Drawing, Abort) => { - responses.add(DocumentMessage::AbortTransaction); - - shape_data.cleanup(responses); - - Ready - } - (_, WorkingColorChanged) => { - responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + match (self, event) { + (Drawing, CanvasTransformed) => { + tool_data.data.recalculate_snaps(document, input, render_data); + self } - } else { - self + (Ready, DragStart) => { + shape_data.start(responses, document, input, render_data); + + let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE); + + let layer_path = document.get_path_for_new_layer(); + responses.add(DocumentMessage::StartTransaction); + shape_data.path = Some(layer_path.clone()); + graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); + + let fill_color = tool_options.fill.active_color(); + responses.add(GraphOperationMessage::FillSet { + layer: layer_path.clone(), + fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None }, + }); + + responses.add(GraphOperationMessage::StrokeSet { + layer: layer_path, + stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), + }); + + Drawing + } + (state, Resize { center, lock_ratio }) => { + if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) { + responses.add(message); + } + + state + } + (Drawing, DragStop) => { + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); + shape_data.cleanup(responses); + + Ready + } + (Drawing, Abort) => { + responses.add(DocumentMessage::AbortTransaction); + + shape_data.cleanup(responses); + + Ready + } + (_, WorkingColorChanged) => { + responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 447b9a13e1..84a062eb76 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,19 +1,14 @@ #![allow(clippy::too_many_arguments)] +use super::tool_prelude::*; use crate::application::generate_uuid; use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; -use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::transformation::Selected; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::path_outline::*; use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::snapping::{self, SnapManager}; 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::document::Document; use document_legacy::intersection::Quad; @@ -21,8 +16,6 @@ use document_legacy::layers::layer_info::{Layer, LayerDataType}; use document_legacy::LayerId; use document_legacy::Operation; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Default)] @@ -446,268 +439,137 @@ impl Fsm for SelectToolFsmState { use SelectToolFsmState::*; use SelectToolMessage::*; - if let ToolMessage::Select(event) = event { - match (self, event) { - (_, DocumentIsDirty | SelectionChanged) => { - let selected_layers_count = document.selected_layers().count(); - let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; + let ToolMessage::Select(event) = event else { + return self; + }; + match (self, event) { + (_, DocumentIsDirty | SelectionChanged) => { + let selected_layers_count = document.selected_layers().count(); + let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; - if selected_layers_changed { - tool_data.selected_layers_count = selected_layers_count; - tool_data.selected_layers_changed = true; - } else { - tool_data.selected_layers_changed = false; - } - - match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) { - (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses), - (Some(bounds), paths) => { - let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses)); - - bounding_box_overlays.bounds = bounds; - bounding_box_overlays.transform = DAffine2::IDENTITY; - - bounding_box_overlays.transform(responses); - - tool_data.bounding_box_overlays = Some(bounding_box_overlays); - } - (_, _) => {} - }; - - tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); - tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); - tool_data.pivot.update_pivot(document, render_data, responses); - - self + if selected_layers_changed { + tool_data.selected_layers_count = selected_layers_count; + tool_data.selected_layers_changed = true; + } else { + tool_data.selected_layers_changed = false; } - (_, EditLayer) => { - // Setup required data for checking the clicked layer - let mouse_pos = input.mouse.position; - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); - // Check the last (topmost) intersection layer - if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { - if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) { - match tool_data.nested_selection_behavior { - NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses), - NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses), - } + match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) { + (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses), + (Some(bounds), paths) => { + let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses)); + + bounding_box_overlays.bounds = bounds; + bounding_box_overlays.transform = DAffine2::IDENTITY; + + bounding_box_overlays.transform(responses); + + tool_data.bounding_box_overlays = Some(bounding_box_overlays); + } + (_, _) => {} + }; + + tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); + tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); + tool_data.pivot.update_pivot(document, render_data, responses); + + self + } + (_, EditLayer) => { + // Setup required data for checking the clicked layer + let mouse_pos = input.mouse.position; + let tolerance = DVec2::splat(SELECTION_TOLERANCE); + let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); + + // Check the last (topmost) intersection layer + if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { + if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) { + match tool_data.nested_selection_behavior { + NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses), + NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses), } } - - self } - (Ready, DragStart { add_to_selection, select_deepest }) => { - tool_data.path_outlines.clear_hovered(responses); - tool_data.drag_start = input.mouse.position; - tool_data.drag_current = input.mouse.position; + self + } + (Ready, DragStart { add_to_selection, select_deepest }) => { + tool_data.path_outlines.clear_hovered(responses); - let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| { - let edges = bounding_box.check_selected_edges(input.mouse.position); + tool_data.drag_start = input.mouse.position; + tool_data.drag_current = input.mouse.position; - bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { - let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); - bounding_box.opposite_pivot = selected_edges.calculate_pivot(); - selected_edges - }); + let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| { + let edges = bounding_box.check_selected_edges(input.mouse.position); - edges + bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { + let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds); + bounding_box.opposite_pivot = selected_edges.calculate_pivot(); + selected_edges }); - let rotating_bounds = tool_data - .bounding_box_overlays - .as_ref() - .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) - .unwrap_or_default(); + edges + }); - let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect(); - let quad = tool_data.selection_quad(); - let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data); + let rotating_bounds = tool_data + .bounding_box_overlays + .as_ref() + .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) + .unwrap_or_default(); - // If the user is dragging the bounding box bounds, go into ResizingBounds mode. - // If the user is dragging the rotate trigger, go into RotatingBounds mode. - // If the user clicks on a layer that is in their current selection, go into the dragging mode. - // If the user clicks on new shape, make that layer their new selection. - // Otherwise enter the box select mode - let state = if tool_data.pivot.is_over(input.mouse.position) { - responses.add(DocumentMessage::StartTransaction); + let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect(); + let quad = tool_data.selection_quad(); + let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data); - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + // If the user is dragging the bounding box bounds, go into ResizingBounds mode. + // If the user is dragging the rotate trigger, go into RotatingBounds mode. + // If the user clicks on a layer that is in their current selection, go into the dragging mode. + // If the user clicks on new shape, make that layer their new selection. + // Otherwise enter the box select mode + let state = if tool_data.pivot.is_over(input.mouse.position) { + responses.add(DocumentMessage::StartTransaction); - DraggingPivot - } else if let Some(selected_edges) = dragging_bounds { - responses.add(DocumentMessage::StartTransaction); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - let snap_x = selected_edges.2 || selected_edges.3; - let snap_y = selected_edges.0 || selected_edges.1; + DraggingPivot + } else if let Some(selected_edges) = dragging_bounds { + responses.add(DocumentMessage::StartTransaction); - tool_data - .snap_manager - .start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y); - tool_data - .snap_manager - .add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::>(), &[]); + let snap_x = selected_edges.2 || selected_edges.3; + let snap_y = selected_edges.0 || selected_edges.1; - tool_data.layers_dragging = selected; + tool_data + .snap_manager + .start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y); + tool_data + .snap_manager + .add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::>(), &[]); - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - let document = &document.document_legacy; + tool_data.layers_dragging = selected; - let selected = &tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - selected, - responses, - document, - None, - &ToolType::Select, - ); - bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); - } - - ResizingBounds - } else if rotating_bounds { - responses.add(DocumentMessage::StartTransaction); - - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - let selected = selected.iter().collect::>(); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &selected, - responses, - &document.document_legacy, - None, - &ToolType::Select, - ); - - bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); - } - - tool_data.layers_dragging = selected; - - RotatingBounds - } else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false) - && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest - { - responses.add(DocumentMessage::StartTransaction); - - tool_data.layers_dragging = selected; - - tool_data - .snap_manager - .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); - - Dragging - } else { - if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { - responses.add(DocumentMessage::DeselectAllLayers); - tool_data.layers_dragging.clear(); - } - - if let Some(intersection) = intersection.pop() { - tool_data.layer_selected_on_start = Some(intersection.clone()); - selected = vec![intersection.clone()]; - - match tool_data.nested_selection_behavior { - NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses), - NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data), - } - Dragging - } else { - // Deselect all layers if using shallowest selection behavior - // Necessary since for shallowest mode, we need to know the current selected layers to determine the next - if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest { - responses.add(DocumentMessage::DeselectAllLayers); - tool_data.layers_dragging.clear(); - } - tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses)); - DrawingBox - } - }; - tool_data.not_duplicated_layers = None; - - state - } - (Dragging, PointerMove { axis_align, duplicate, .. }) => { - tool_data.is_dragging = true; - // TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here. - responses.add_front(SelectToolMessage::DocumentIsDirty); - - let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start); - - let mouse_delta = mouse_position - tool_data.drag_current; - - let snap = tool_data - .layers_dragging - .iter() - .filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?) - .flat_map(snapping::expand_bounds) - .collect(); - - let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); - // 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.add_front(GraphOperationMessage::TransformChange { - layer: path.to_vec(), - transform: DAffine2::from_translation(mouse_delta + closest_move), - transform_in: TransformIn::Viewport, - skip_rerender: true, - }); - } - tool_data.drag_current = mouse_position + closest_move; - - if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() { - tool_data.start_duplicates(document, responses); - } else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() { - tool_data.stop_duplicates(responses); - } - - Dragging - } - (ResizingBounds, PointerMove { axis_align, center, .. }) => { if let Some(bounds) = &mut tool_data.bounding_box_overlays { - if let Some(movement) = &mut bounds.selected_edges { - let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize)); + let document = &document.document_legacy; - let mouse_position = input.mouse.position; - - let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); - - let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align); - let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size); - - let selected = &tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select); - - selected.update_transforms(delta); - } + let selected = &tool_data.layers_dragging.iter().collect::>(); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + selected, + responses, + document, + None, + &ToolType::Select, + ); + bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); } + ResizingBounds - } - (RotatingBounds, PointerMove { snap_angle, .. }) => { + } else if rotating_bounds { + responses.add(DocumentMessage::StartTransaction); + if let Some(bounds) = &mut tool_data.bounding_box_overlays { - let angle = { - let start_offset = tool_data.drag_start - bounds.center_of_transformation; - let end_offset = input.mouse.position - bounds.center_of_transformation; - - start_offset.angle_between(end_offset) - }; - - let snapped_angle = if input.keyboard.get(snap_angle as usize) { - let snap_resolution = ROTATE_SNAP_ANGLE.to_radians(); - (angle / snap_resolution).round() * snap_resolution - } else { - angle - }; - - let delta = DAffine2::from_angle(snapped_angle); - - let selected = tool_data.layers_dragging.iter().collect::>(); + let selected = selected.iter().collect::>(); let mut selected = Selected::new( &mut bounds.original_transforms, &mut bounds.center_of_transformation, @@ -718,233 +580,363 @@ impl Fsm for SelectToolFsmState { &ToolType::Select, ); - selected.update_transforms(delta); + bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); } + tool_data.layers_dragging = selected; + RotatingBounds - } - (DraggingPivot, PointerMove { .. }) => { - let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); - tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses); + } else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false) + && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest + { + responses.add(DocumentMessage::StartTransaction); - DraggingPivot - } - (DrawingBox, PointerMove { .. }) => { - tool_data.drag_current = input.mouse.position; + tool_data.layers_dragging = selected; - responses.add_front(DocumentMessage::Overlays( - Operation::SetLayerTransformInViewport { - path: tool_data.drag_box_overlay_layer.clone().unwrap(), - transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(), - } - .into(), - )); - DrawingBox - } - (Ready, PointerMove { .. }) => { - let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); + tool_data + .snap_manager + .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); - // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position) { - cursor = MouseCursorIcon::Move; + Dragging + } else { + if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { + responses.add(DocumentMessage::DeselectAllLayers); + tool_data.layers_dragging.clear(); } - // Generate the select outline (but not if the user is going to use the bound overlays) - if cursor == MouseCursorIcon::Default { - tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); + if let Some(intersection) = intersection.pop() { + tool_data.layer_selected_on_start = Some(intersection.clone()); + selected = vec![intersection]; + + match tool_data.nested_selection_behavior { + NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses), + NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data), + } + Dragging } else { - tool_data.path_outlines.clear_hovered(responses); - } - - if tool_data.cursor != cursor { - tool_data.cursor = cursor; - responses.add(FrontendMessage::UpdateMouseCursor { cursor }); - } - - Ready - } - (Dragging, Enter) => { - rerender_selected_layers(tool_data, responses); - - let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { - true => DocumentMessage::Undo, - false => DocumentMessage::CommitTransaction, - }; - tool_data.snap_manager.cleanup(responses); - responses.add_front(response); - - Ready - } - (Dragging, DragStop { remove_from_selection }) => { - rerender_selected_layers(tool_data, responses); - - // Deselect layer if not snap dragging - if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() { - let quad = tool_data.selection_quad(); - let intersection = document.document_legacy.intersects_quad_root(quad, render_data); - - if let Some(path) = intersection.last() { - let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect(); - + // Deselect all layers if using shallowest selection behavior + // Necessary since for shallowest mode, we need to know the current selected layers to determine the next + if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest { + responses.add(DocumentMessage::DeselectAllLayers); tool_data.layers_dragging.clear(); - tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut()); - - responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); } + tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses)); + DrawingBox } + }; + tool_data.not_duplicated_layers = None; - tool_data.is_dragging = false; - tool_data.layer_selected_on_start = None; + state + } + (Dragging, PointerMove { axis_align, duplicate, .. }) => { + tool_data.is_dragging = true; + // TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here. + responses.add_front(SelectToolMessage::DocumentIsDirty); - responses.add(DocumentMessage::CommitTransaction); - tool_data.snap_manager.cleanup(responses); + let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start); - Ready - } - (ResizingBounds, DragStop { .. } | Enter) => { - rerender_selected_layers(tool_data, responses); + let mouse_delta = mouse_position - tool_data.drag_current; - let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { - true => DocumentMessage::Undo, - false => DocumentMessage::CommitTransaction, - }; - responses.add(response); + let snap = tool_data + .layers_dragging + .iter() + .filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?) + .flat_map(snapping::expand_bounds) + .collect(); - tool_data.snap_manager.cleanup(responses); - - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - bounds.original_transforms.clear(); - } - - Ready - } - (RotatingBounds, DragStop { .. } | Enter) => { - rerender_selected_layers(tool_data, responses); - - let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { - true => DocumentMessage::Undo, - false => DocumentMessage::CommitTransaction, - }; - responses.add(response); - - if let Some(bounds) = &mut tool_data.bounding_box_overlays { - bounds.original_transforms.clear(); - } - - Ready - } - (DraggingPivot, DragStop { .. } | Enter) => { - let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { - true => DocumentMessage::Undo, - false => DocumentMessage::CommitTransaction, - }; - responses.add(response); - - tool_data.snap_manager.cleanup(responses); - - Ready - } - (DrawingBox, DragStop { .. } | Enter) => { - let quad = tool_data.selection_quad(); - // For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation() - tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data); - responses.add_front(DocumentMessage::AddSelectedLayers { - additional_layers: document.document_legacy.intersects_quad_root(quad, render_data), + let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); + // 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.add_front(GraphOperationMessage::TransformChange { + layer: path.to_vec(), + transform: DAffine2::from_translation(mouse_delta + closest_move), + transform_in: TransformIn::Viewport, + skip_rerender: true, }); - responses.add_front(DocumentMessage::Overlays( - Operation::DeleteLayer { - path: tool_data.drag_box_overlay_layer.take().unwrap(), - } - .into(), - )); - Ready } - (Ready, Enter) => { - let mut selected_layers = document.selected_layers(); + tool_data.drag_current = mouse_position + closest_move; - if let Some(layer_path) = selected_layers.next() { - // Check that only one layer is selected - if selected_layers.next().is_none() { - if let Ok(layer) = document.document_legacy.layer(layer_path) { - if let Ok(network) = layer.as_layer_network() { - if network.nodes.values().any(|node| node.name == "Text") { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }); - responses.add(TextToolMessage::EditSelected); - } + if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() { + tool_data.start_duplicates(document, responses); + } else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() { + tool_data.stop_duplicates(responses); + } + + Dragging + } + (ResizingBounds, PointerMove { axis_align, center, .. }) => { + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + if let Some(movement) = &mut bounds.selected_edges { + let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize)); + + let mouse_position = input.mouse.position; + + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); + + let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align); + let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size); + + let selected = &tool_data.layers_dragging.iter().collect::>(); + let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select); + + selected.update_transforms(delta); + } + } + ResizingBounds + } + (RotatingBounds, PointerMove { snap_angle, .. }) => { + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + let angle = { + let start_offset = tool_data.drag_start - bounds.center_of_transformation; + let end_offset = input.mouse.position - bounds.center_of_transformation; + + start_offset.angle_between(end_offset) + }; + + let snapped_angle = if input.keyboard.get(snap_angle as usize) { + let snap_resolution = ROTATE_SNAP_ANGLE.to_radians(); + (angle / snap_resolution).round() * snap_resolution + } else { + angle + }; + + let delta = DAffine2::from_angle(snapped_angle); + + let selected = tool_data.layers_dragging.iter().collect::>(); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &selected, + responses, + &document.document_legacy, + None, + &ToolType::Select, + ); + + selected.update_transforms(delta); + } + + RotatingBounds + } + (DraggingPivot, PointerMove { .. }) => { + let mouse_position = input.mouse.position; + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); + tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses); + + DraggingPivot + } + (DrawingBox, PointerMove { .. }) => { + tool_data.drag_current = input.mouse.position; + + responses.add_front(DocumentMessage::Overlays( + Operation::SetLayerTransformInViewport { + path: tool_data.drag_box_overlay_layer.clone().unwrap(), + transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(), + } + .into(), + )); + DrawingBox + } + (Ready, PointerMove { .. }) => { + let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); + + // Dragging the pivot overrules the other operations + if tool_data.pivot.is_over(input.mouse.position) { + cursor = MouseCursorIcon::Move; + } + + // Generate the select outline (but not if the user is going to use the bound overlays) + if cursor == MouseCursorIcon::Default { + tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); + } else { + tool_data.path_outlines.clear_hovered(responses); + } + + if tool_data.cursor != cursor { + tool_data.cursor = cursor; + responses.add(FrontendMessage::UpdateMouseCursor { cursor }); + } + + Ready + } + (Dragging, Enter) => { + rerender_selected_layers(tool_data, responses); + + let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { + true => DocumentMessage::Undo, + false => DocumentMessage::CommitTransaction, + }; + tool_data.snap_manager.cleanup(responses); + responses.add_front(response); + + Ready + } + (Dragging, DragStop { remove_from_selection }) => { + rerender_selected_layers(tool_data, responses); + + // Deselect layer if not snap dragging + if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() { + let quad = tool_data.selection_quad(); + let intersection = document.document_legacy.intersects_quad_root(quad, render_data); + + if let Some(path) = intersection.last() { + let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect(); + + tool_data.layers_dragging.clear(); + tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut()); + + responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); + } + } + + tool_data.is_dragging = false; + tool_data.layer_selected_on_start = None; + + responses.add(DocumentMessage::CommitTransaction); + tool_data.snap_manager.cleanup(responses); + + Ready + } + (ResizingBounds, DragStop { .. } | Enter) => { + rerender_selected_layers(tool_data, responses); + + let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { + true => DocumentMessage::Undo, + false => DocumentMessage::CommitTransaction, + }; + responses.add(response); + + tool_data.snap_manager.cleanup(responses); + + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + bounds.original_transforms.clear(); + } + + Ready + } + (RotatingBounds, DragStop { .. } | Enter) => { + rerender_selected_layers(tool_data, responses); + + let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { + true => DocumentMessage::Undo, + false => DocumentMessage::CommitTransaction, + }; + responses.add(response); + + if let Some(bounds) = &mut tool_data.bounding_box_overlays { + bounds.original_transforms.clear(); + } + + Ready + } + (DraggingPivot, DragStop { .. } | Enter) => { + let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { + true => DocumentMessage::Undo, + false => DocumentMessage::CommitTransaction, + }; + responses.add(response); + + tool_data.snap_manager.cleanup(responses); + + Ready + } + (DrawingBox, DragStop { .. } | Enter) => { + let quad = tool_data.selection_quad(); + // For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation() + tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data); + responses.add_front(DocumentMessage::AddSelectedLayers { + additional_layers: document.document_legacy.intersects_quad_root(quad, render_data), + }); + responses.add_front(DocumentMessage::Overlays( + Operation::DeleteLayer { + path: tool_data.drag_box_overlay_layer.take().unwrap(), + } + .into(), + )); + Ready + } + (Ready, Enter) => { + let mut selected_layers = document.selected_layers(); + + if let Some(layer_path) = selected_layers.next() { + // Check that only one layer is selected + if selected_layers.next().is_none() { + if let Ok(layer) = document.document_legacy.layer(layer_path) { + if let Ok(network) = layer.as_layer_network() { + if network.nodes.values().any(|node| node.name == "Text") { + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }); + responses.add(TextToolMessage::EditSelected); } } } } - - Ready } - (Dragging, Abort) => { - rerender_selected_layers(tool_data, responses); - tool_data.snap_manager.cleanup(responses); - responses.add(DocumentMessage::Undo); - - tool_data.path_outlines.clear_selected(responses); - tool_data.pivot.clear_overlays(responses); - - Ready - } - (_, Abort) => { - if let Some(path) = tool_data.drag_box_overlay_layer.take() { - responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into())) - }; - if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() { - let selected = tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new( - &mut bounding_box_overlays.original_transforms, - &mut bounding_box_overlays.opposite_pivot, - &selected, - responses, - &document.document_legacy, - None, - &ToolType::Select, - ); - - selected.revert_operation(); - - bounding_box_overlays.delete(responses); - } - - tool_data.path_outlines.clear_hovered(responses); - tool_data.path_outlines.clear_selected(responses); - tool_data.pivot.clear_overlays(responses); - - tool_data.snap_manager.cleanup(responses); - Ready - } - (_, Align { axis, aggregate }) => { - responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate }); - - self - } - (_, FlipHorizontal) => { - responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X }); - - self - } - (_, FlipVertical) => { - responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y }); - - self - } - (_, SetPivot { position }) => { - responses.add(DocumentMessage::StartTransaction); - - let pos: Option = position.into(); - tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses); - - self - } - _ => self, + Ready } - } else { - self + (Dragging, Abort) => { + rerender_selected_layers(tool_data, responses); + + tool_data.snap_manager.cleanup(responses); + responses.add(DocumentMessage::Undo); + + tool_data.path_outlines.clear_selected(responses); + tool_data.pivot.clear_overlays(responses); + + Ready + } + (_, Abort) => { + if let Some(path) = tool_data.drag_box_overlay_layer.take() { + responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into())) + }; + if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() { + let selected = tool_data.layers_dragging.iter().collect::>(); + let mut selected = Selected::new( + &mut bounding_box_overlays.original_transforms, + &mut bounding_box_overlays.opposite_pivot, + &selected, + responses, + &document.document_legacy, + None, + &ToolType::Select, + ); + + selected.revert_operation(); + + bounding_box_overlays.delete(responses); + } + + tool_data.path_outlines.clear_hovered(responses); + tool_data.path_outlines.clear_selected(responses); + tool_data.pivot.clear_overlays(responses); + + tool_data.snap_manager.cleanup(responses); + Ready + } + (_, Align { axis, aggregate }) => { + responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate }); + + self + } + (_, FlipHorizontal) => { + responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X }); + + self + } + (_, FlipVertical) => { + responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y }); + + self + } + (_, SetPivot { position }) => { + responses.add(DocumentMessage::StartTransaction); + + let pos: Option = position.into(); + tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses); + + self + } + _ => self, } } @@ -1117,7 +1109,7 @@ fn drag_shallowest_manipulation( combined_layers.push(incoming_layer_path_vector); } // Shared shallowest common folder of the combined layers - let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.iter().copied()).unwrap_or_default().to_vec(); + let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.into_iter()).unwrap_or_default().to_vec(); let mut selected_layer_path_parent = shallowest_common_folder.to_vec(); // Determine if the incoming layer path is already selected @@ -1175,12 +1167,12 @@ fn drag_shallowest_manipulation( } } else if selected_layers_count > 1 { let direct_child = incoming_layer_path_vector - .iter() - .copied() + .into_iter() .filter(|path| !shallowest_common_folder.contains(path)) .take(1) + .copied() .collect::>(); - let already_selected_direct_child = selected_layers_collected.contains(&direct_child.clone().as_slice()); + let already_selected_direct_child = selected_layers_collected.contains(&direct_child.as_slice()); // Update layer tree by filtering any duplicate layers (e.g. If a parent and one of its children are selected) let mut replacement_selected_layers: Vec> = Vec::new(); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 3eff6875e7..49d3109c67 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,21 +1,13 @@ +use super::tool_prelude::*; use crate::consts::DRAG_THRESHOLD; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::{LayerId, Operation}; use graphene_core::vector::style::{Fill, Stroke}; use graphene_core::Color; -use glam::DVec2; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct SplineTool { fsm_state: SplineToolFsmState, @@ -128,33 +120,31 @@ impl LayoutHolder for SplineTool { 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, - SplineOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - SplineOptionsUpdate::StrokeColor(color) => { - self.options.stroke.custom_color = color; - self.options.stroke.color_type = ToolColorType::Custom; - } - SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, - SplineOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.stroke.primary_working_color = primary; - self.options.stroke.secondary_working_color = secondary; - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + match action { + SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, + SplineOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + SplineOptionsUpdate::StrokeColor(color) => { + self.options.stroke.custom_color = color; + self.options.stroke.color_type = ToolColorType::Custom; + } + SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type, + SplineOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.stroke.primary_working_color = primary; + self.options.stroke.secondary_working_color = secondary; + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -219,86 +209,85 @@ impl Fsm for SplineToolFsmState { use SplineToolFsmState::*; use SplineToolMessage::*; - let transform = document.document_legacy.root.transform; + let transform = document.document_legacy.metadata.document_to_viewport; - if let ToolMessage::Spline(event) = event { - match (self, event) { - (_, CanvasTransformed) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - self - } - (Ready, DragStart) => { - responses.add(DocumentMessage::StartTransaction); - responses.add(DocumentMessage::DeselectAllLayers); - tool_data.path = Some(document.get_path_for_new_layer()); - - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); - tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - - let pos = transform.inverse().transform_point2(snapped_position); - - tool_data.points.push(pos); - tool_data.next_point = pos; - - tool_data.weight = tool_options.line_weight; - - add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); - - Drawing - } - (Drawing, DragStop) => { - let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let pos = transform.inverse().transform_point2(snapped_position); - - if let Some(last_pos) = tool_data.points.last() { - if last_pos.distance(pos) > DRAG_THRESHOLD { - tool_data.points.push(pos); - tool_data.next_point = pos; - } - } - - responses.add(remove_preview(tool_data)); - add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); - - Drawing - } - (Drawing, PointerMove) => { - let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let pos = transform.inverse().transform_point2(snapped_position); - tool_data.next_point = pos; - - responses.add(remove_preview(tool_data)); - add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); - - Drawing - } - (Drawing, Confirm) | (Drawing, Abort) => { - if tool_data.points.len() >= 2 { - responses.add(remove_preview(tool_data)); - add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); - responses.add(DocumentMessage::CommitTransaction); - } else { - responses.add(DocumentMessage::AbortTransaction); - } - - tool_data.path = None; - tool_data.points.clear(); - tool_data.snap_manager.cleanup(responses); - - Ready - } - (_, WorkingColorChanged) => { - responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + let ToolMessage::Spline(event) = event else { + return self; + }; + match (self, event) { + (_, CanvasTransformed) => { + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + self } - } else { - self + (Ready, DragStart) => { + responses.add(DocumentMessage::StartTransaction); + responses.add(DocumentMessage::DeselectAllLayers); + tool_data.path = Some(document.get_path_for_new_layer()); + + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + + let pos = transform.inverse().transform_point2(snapped_position); + + tool_data.points.push(pos); + tool_data.next_point = pos; + + tool_data.weight = tool_options.line_weight; + + add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); + + Drawing + } + (Drawing, DragStop) => { + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + let pos = transform.inverse().transform_point2(snapped_position); + + if let Some(last_pos) = tool_data.points.last() { + if last_pos.distance(pos) > DRAG_THRESHOLD { + tool_data.points.push(pos); + tool_data.next_point = pos; + } + } + + responses.add(remove_preview(tool_data)); + add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); + + Drawing + } + (Drawing, PointerMove) => { + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + let pos = transform.inverse().transform_point2(snapped_position); + tool_data.next_point = pos; + + responses.add(remove_preview(tool_data)); + add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); + + Drawing + } + (Drawing, Confirm) | (Drawing, Abort) => { + if tool_data.points.len() >= 2 { + responses.add(remove_preview(tool_data)); + add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses); + responses.add(DocumentMessage::CommitTransaction); + } else { + responses.add(DocumentMessage::AbortTransaction); + } + + tool_data.path = None; + tool_data.points.clear(); + tool_data.snap_manager.cleanup(responses); + + Ready + } + (_, WorkingColorChanged) => { + responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 76c52de630..cc28096e8a 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -1,15 +1,9 @@ #![allow(clippy::too_many_arguments)] - +use super::tool_prelude::*; use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE}; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; -use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::new_text_network; -use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; -use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::intersection::Quad; use document_legacy::layers::layer_info::Layer; @@ -21,9 +15,6 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork}; use graphene_core::text::{load_face, Font}; use graphene_core::Color; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; - #[derive(Default)] pub struct TextTool { fsm_state: TextToolFsmState, @@ -154,32 +145,30 @@ impl LayoutHolder for TextTool { 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 } => { - self.options.font_name = family; - self.options.font_style = style; - - self.send_layout(responses, LayoutTarget::ToolOptions); - } - TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size, - TextOptionsUpdate::FillColor(color) => { - self.options.fill.custom_color = color; - self.options.fill.color_type = ToolColorType::Custom; - } - TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, - TextOptionsUpdate::WorkingColors(primary, secondary) => { - self.options.fill.primary_working_color = primary; - self.options.fill.secondary_working_color = secondary; - } - } - - self.send_layout(responses, LayoutTarget::ToolOptions); - + let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); return; + }; + match action { + TextOptionsUpdate::Font { family, style } => { + self.options.font_name = family; + self.options.font_style = style; + + self.send_layout(responses, LayoutTarget::ToolOptions); + } + TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size, + TextOptionsUpdate::FillColor(color) => { + self.options.fill.custom_color = color; + self.options.fill.color_type = ToolColorType::Custom; + } + TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type, + TextOptionsUpdate::WorkingColors(primary, secondary) => { + self.options.fill.primary_working_color = primary; + self.options.fill.secondary_working_color = secondary; + } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.send_layout(responses, LayoutTarget::ToolOptions); } fn actions(&self) -> ActionList { @@ -503,88 +492,87 @@ impl Fsm for TextToolFsmState { render_data, .. } = transition_data; - if let ToolMessage::Text(event) = event { - match (self, event) { - (TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => { - responses.add(FrontendMessage::DisplayEditableTextboxTransform { - transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(), - }); - tool_data.update_bounds_overlay(document, render_data, responses); - TextToolFsmState::Editing - } - (state, TextToolMessage::DocumentIsDirty) => { - update_overlays(document, tool_data, responses, render_data); - - state - } - (state, TextToolMessage::Interact) => { - tool_data.editing_text = Some(EditingText { - text: String::new(), - transform: DAffine2::from_translation(input.mouse.position), - font_size: tool_options.font_size as f64, - font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()), - color: tool_options.fill.active_color(), - }); - tool_data.new_text = String::new(); - tool_data.layer_path = document.get_path_for_new_layer(); - - tool_data.interact(state, input.mouse.position, document, render_data, responses) - } - (state, TextToolMessage::EditSelected) => { - if let Some(layer_path) = can_edit_selected(document) { - tool_data.start_editing_layer(&layer_path, state, document, render_data, responses); - return TextToolFsmState::Editing; - } - - state - } - (state, TextToolMessage::Abort) => { - if state == TextToolFsmState::Editing { - tool_data.set_editing(false, render_data, responses); - } - - resize_overlays(&mut tool_data.overlays, responses, 0); - - TextToolFsmState::Ready - } - (TextToolFsmState::Editing, TextToolMessage::CommitText) => { - responses.add(FrontendMessage::TriggerTextCommit); - - TextToolFsmState::Editing - } - (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => { - let layer_path = tool_data.layer_path.clone(); - let network = get_network(&layer_path, document).unwrap(); - tool_data.fix_text_bounds(&new_text, document, render_data, responses); - responses.add(NodeGraphMessage::SetQualifiedInputValue { - layer_path, - node_path: vec![get_text_node_id(network).unwrap()], - input_index: 1, - value: TaggedValue::String(new_text), - }); - - tool_data.set_editing(false, render_data, responses); - - resize_overlays(&mut tool_data.overlays, responses, 0); - - TextToolFsmState::Ready - } - (TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => { - tool_data.new_text = new_text; - tool_data.update_bounds_overlay(document, render_data, responses); - TextToolFsmState::Editing - } - (_, TextToolMessage::WorkingColorChanged) => { - responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors( - Some(global_tool_data.primary_color), - Some(global_tool_data.secondary_color), - ))); - self - } - _ => self, + let ToolMessage::Text(event) = event else { + return self; + }; + match (self, event) { + (TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => { + responses.add(FrontendMessage::DisplayEditableTextboxTransform { + transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(), + }); + tool_data.update_bounds_overlay(document, render_data, responses); + TextToolFsmState::Editing } - } else { - self + (state, TextToolMessage::DocumentIsDirty) => { + update_overlays(document, tool_data, responses, render_data); + + state + } + (state, TextToolMessage::Interact) => { + tool_data.editing_text = Some(EditingText { + text: String::new(), + transform: DAffine2::from_translation(input.mouse.position), + font_size: tool_options.font_size as f64, + font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()), + color: tool_options.fill.active_color(), + }); + tool_data.new_text = String::new(); + tool_data.layer_path = document.get_path_for_new_layer(); + + tool_data.interact(state, input.mouse.position, document, render_data, responses) + } + (state, TextToolMessage::EditSelected) => { + if let Some(layer_path) = can_edit_selected(document) { + tool_data.start_editing_layer(&layer_path, state, document, render_data, responses); + return TextToolFsmState::Editing; + } + + state + } + (state, TextToolMessage::Abort) => { + if state == TextToolFsmState::Editing { + tool_data.set_editing(false, render_data, responses); + } + + resize_overlays(&mut tool_data.overlays, responses, 0); + + TextToolFsmState::Ready + } + (TextToolFsmState::Editing, TextToolMessage::CommitText) => { + responses.add(FrontendMessage::TriggerTextCommit); + + TextToolFsmState::Editing + } + (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => { + let layer_path = tool_data.layer_path.clone(); + let network = get_network(&layer_path, document).unwrap(); + tool_data.fix_text_bounds(&new_text, document, render_data, responses); + responses.add(NodeGraphMessage::SetQualifiedInputValue { + layer_path, + node_path: vec![get_text_node_id(network).unwrap()], + input_index: 1, + value: TaggedValue::String(new_text), + }); + + tool_data.set_editing(false, render_data, responses); + + resize_overlays(&mut tool_data.overlays, responses, 0); + + TextToolFsmState::Ready + } + (TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => { + tool_data.new_text = new_text; + tool_data.update_bounds_overlay(document, render_data, responses); + TextToolFsmState::Editing + } + (_, TextToolMessage::WorkingColorChanged) => { + responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors( + Some(global_tool_data.primary_color), + Some(global_tool_data.secondary_color), + ))); + self + } + _ => self, } } 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 e2c5ec51a6..5a1fd4f269 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 @@ -5,6 +5,7 @@ use crate::messages::prelude::*; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::{ToolData, ToolType}; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::style::RenderData; use graphene_core::vector::ManipulatorPointId; @@ -228,7 +229,10 @@ impl<'a> MessageHandler> for TransformL self.mouse_position = ipp.mouse.position; } SelectionChanged => { - let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect(); + let layer_paths = document + .selected_visible_layers() + .map(|layer_path| LayerNodeIdentifier::from_path(layer_path, document.network())) + .collect(); shape_editor.set_selected_layers(layer_paths); } TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap), diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index b91498634f..0ca1ccfc54 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,10 +1,12 @@ use crate::messages::frontend::utility_types::FrontendImageData; use crate::messages::portfolio::document::node_graph::wrap_network_in_scope; - +use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry}; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -use document_legacy::layers::layer_info::LayerDataType; +use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document_metadata::LayerNodeIdentifier; +use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant}; use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; @@ -14,8 +16,9 @@ use graph_craft::imaginate_input::ImaginatePreferences; use graph_craft::{concrete, Type, TypeDescriptor}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender}; use graphene_core::raster::{Image, ImageFrame}; -use graphene_core::renderer::{SvgSegment, SvgSegmentList}; +use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList}; use graphene_core::text::FontCache; +use graphene_core::transform::Transform; use graphene_core::vector::style::ViewMode; use graphene_core::{Color, SurfaceFrame, SurfaceId}; @@ -52,7 +55,9 @@ pub struct NodeRuntime { sender: InternalNodeGraphUpdateSender, wasm_io: Option, imaginate_preferences: ImaginatePreferences, - pub(crate) thumbnails: HashMap>, + pub(crate) thumbnails: HashMap, + pub(crate) click_targets: HashMap>, + pub(crate) transforms: HashMap, canvas_cache: HashMap, SurfaceId>, } @@ -73,7 +78,9 @@ pub(crate) struct GenerationResponse { generation_id: u64, result: Result, updates: VecDeque, - new_thumbnails: HashMap>, + new_thumbnails: HashMap, + new_click_targets: HashMap>, + new_transforms: HashMap, } enum NodeGraphUpdate { @@ -110,7 +117,9 @@ impl NodeRuntime { imaginate_preferences: Default::default(), thumbnails: Default::default(), wasm_io: None, - canvas_cache: Default::default(), + canvas_cache: HashMap::new(), + click_targets: HashMap::new(), + transforms: HashMap::new(), } } pub async fn run(&mut self) { @@ -150,6 +159,8 @@ impl NodeRuntime { result, updates: responses, new_thumbnails: self.thumbnails.clone(), + new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(), + new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(), }; self.sender.send_generation_response(response); } @@ -204,41 +215,60 @@ impl NodeRuntime { /// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI. pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: Vec>, responses: &mut VecDeque) { - let mut thumbnails_changed: bool = false; let mut image_data: Vec<_> = Vec::new(); for node_path in monitor_nodes { - let Some(value) = self.executor.introspect(&node_path).flatten() else { - warn!("No introspect"); + let Some(node_id) = node_path.get(node_path.len() - 2).copied() else { + warn!("Monitor node has invalid node id"); continue; }; - let Some(graphic_group) = value.downcast_ref::() else { - warn!("Not graphic"); + let Some(value) = self.executor.introspect(&node_path).flatten() else { + warn!("Failed to introspect monitor node for thumbnail"); + continue; + }; + let Some(graphic_element_data) = value.downcast_ref::() else { + warn!("Failed to downcast thumbnail to graphic element data"); continue; }; use graphene_core::renderer::*; - let bounds = graphic_group.bounding_box(DAffine2::IDENTITY); + let bounds = graphic_element_data.bounding_box(DAffine2::IDENTITY); let render_params = RenderParams::new(ViewMode::Normal, bounds, true); let mut render = SvgRender::new(); - graphic_group.render_svg(&mut render, &render_params); + graphic_element_data.render_svg(&mut render, &render_params); let [min, max] = bounds.unwrap_or_default(); render.format_svg(min, max); - if let Some(node_id) = node_path.get(node_path.len() - 2).copied() { - let graph_identifier = GraphIdentifier::new(layer_path.last().copied()); - let old_thumbnail = self.thumbnails.entry(graph_identifier).or_default().entry(node_id).or_default(); - if *old_thumbnail != render.svg { - *old_thumbnail = render.svg; - thumbnails_changed = true; - } + let click_targets = self.click_targets.entry(node_id).or_default(); + click_targets.clear(); + graphic_element_data.add_click_targets(click_targets); + + self.transforms.insert(node_id, graphic_element_data.transform()); + + let old_thumbnail = self.thumbnails.entry(node_id).or_default(); + if *old_thumbnail != render.svg { + responses.add(FrontendMessage::UpdateDocumentLayerDetails { + data: LayerPanelEntry { + name: "Layer".to_string(), + tooltip: format!("Layer id: {node_id}"), + visible: true, + layer_type: LayerDataTypeDiscriminant::Layer, + layer_metadata: LayerMetadata::new(true), + path: vec![node_id], + thumbnail: render.svg.to_string(), + }, + }); + responses.add(FrontendMessage::UpdateNodeThumbnail { + id: node_id, + value: render.svg.to_string(), + }); + *old_thumbnail = render.svg; } + let resize = Some(DVec2::splat(100.)); let create_image_data = |(node_id, image)| NodeGraphExecutor::to_frontend_image_data(image, None, layer_path, Some(node_id), resize).ok(); image_data.extend(render.image_data.into_iter().filter_map(create_image_data)) } if !image_data.is_empty() { responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data }); - } else if thumbnails_changed { - responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } } } @@ -279,7 +309,7 @@ pub struct NodeGraphExecutor { receiver: Receiver, // TODO: This is a memory leak since layers are never removed pub(crate) last_output_type: HashMap, Option>, - pub(crate) thumbnails: HashMap>, + pub(crate) thumbnails: HashMap, futures: HashMap, } @@ -408,7 +438,7 @@ impl NodeGraphExecutor { // Get the node graph layer let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?; let network = if layer_path.is_empty() { - document.document_legacy.document_network.clone() + document.network().clone() } else { let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?; @@ -431,7 +461,7 @@ impl NodeGraphExecutor { Ok(()) } - pub fn poll_node_graph_evaluation(&mut self, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { + pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentLegacy, responses: &mut VecDeque) -> Result<(), String> { let results = self.receiver.try_iter().collect::>(); for response in results { match response { @@ -440,12 +470,16 @@ impl NodeGraphExecutor { result, updates, new_thumbnails, + new_click_targets, + new_transforms, }) => { self.thumbnails = new_thumbnails; + document.metadata.update_transforms(new_transforms); + document.metadata.update_click_targets(new_click_targets); let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?; let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?; responses.extend(updates); - self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?; + self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?; responses.add(DocumentMessage::LayerChanged { affected_layer_path: execution_context.layer_path, }); @@ -464,7 +498,7 @@ impl NodeGraphExecutor { Ok(()) } - fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec, _transform: DAffine2, responses: &mut VecDeque, document_id: u64) -> Result<(), String> { + fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec, responses: &mut VecDeque, document_id: u64) -> Result<(), String> { self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty())); match node_graph_output { TaggedValue::VectorData(vector_data) => { @@ -528,12 +562,17 @@ impl NodeGraphExecutor { } /// When a blob url for a thumbnail is loaded, update the state and the UI. - pub fn insert_thumbnail_blob_url(&mut self, blob_url: String, layer_id: Option, node_id: NodeId, responses: &mut VecDeque) { - if let Some(layer) = self.thumbnails.get_mut(&GraphIdentifier::new(layer_id)) { - if let Some(segment) = layer.values_mut().flat_map(|segments| segments.iter_mut()).find(|segment| **segment == SvgSegment::BlobUrl(node_id)) { + pub fn insert_thumbnail_blob_url(&mut self, blob_url: String, node_id: NodeId, responses: &mut VecDeque) { + for segment_list in self.thumbnails.values_mut() { + if let Some(segment) = segment_list.iter_mut().find(|segment| **segment == SvgSegment::BlobUrl(node_id)) { *segment = SvgSegment::String(blob_url); - responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); + responses.add(FrontendMessage::UpdateNodeThumbnail { + id: node_id, + value: segment_list.to_string(), + }); + return; } } + warn!("Recieved blob url for invalid segment") } } diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index c4b909427d..a93fb1831e 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -652,8 +652,8 @@
- {#if node.thumbnailSvg} - {@html node.thumbnailSvg} + {#if $nodeGraph.thumbnails.has(node.id)} + {@html $nodeGraph.thumbnails.get(node.id) } {/if} {#if node.primaryOutput} (), }); // Set up message subscriptions on creation @@ -24,6 +26,13 @@ export function createNodeGraphState(editor: Editor) { update((state) => { state.nodes = updateNodeGraph.nodes; state.links = updateNodeGraph.links; + let newThumbnails = new Map(); + state.nodes.forEach((node) => { + const thumbnail = state.thumbnails.get(node.id); + if (thumbnail) + newThumbnails.set(node.id, thumbnail); + }); + state.thumbnails = newThumbnails; return state; }); }); @@ -33,6 +42,12 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (updateNodeThumbnail) => { + update((state) => { + state.thumbnails.set(updateNodeThumbnail.id, updateNodeThumbnail.value); + return state; + }); + }); editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => { update((state) => { state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll; diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 11667336f1..d350da16ef 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -37,6 +37,12 @@ export class UpdateNodeTypes extends JsMessage { readonly nodeTypes!: FrontendNodeType[]; } +export class UpdateNodeThumbnail extends JsMessage { + readonly id!: bigint; + + readonly value!: string; +} + export class UpdateNodeGraphSelection extends JsMessage { @Type(() => BigInt) readonly selected!: bigint[]; @@ -106,8 +112,6 @@ export class FrontendNode { readonly previewed!: boolean; readonly disabled!: boolean; - - readonly thumbnailSvg!: string | undefined; } export class FrontendNodeLink { @@ -1435,6 +1439,7 @@ export const messageMakers: Record = { UpdateNodeGraph, UpdateNodeGraphBarLayout, UpdateNodeGraphSelection, + UpdateNodeThumbnail, UpdateNodeTypes, UpdateOpenDocumentsList, UpdatePropertyPanelOptionsLayout, diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index ce82d587de..fa271c6b29 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -97,6 +97,13 @@ fn construct_layer>( stack } +pub struct ToGraphicElementData {} + +#[node_fn(ToGraphicElementData)] +fn to_graphic_element_data>(graphic_element_data: Data) -> GraphicElementData { + graphic_element_data.into() +} + pub struct ConstructArtboardNode { location: Location, dimensions: Dimensions, diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 1e09aefff3..18e19a03ff 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -1,11 +1,56 @@ use crate::raster::{Image, ImageFrame}; -use crate::{uuid::generate_uuid, vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup}; -use quad::Quad; +use crate::uuid::{generate_uuid, ManipulatorGroupId}; +use crate::{vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup}; +use bezier_rs::Subpath; +pub use quad::Quad; use glam::{DAffine2, DVec2}; mod quad; +/// Represents a clickable target for the layer +#[derive(Clone, Debug)] +pub struct ClickTarget { + pub subpath: bezier_rs::Subpath, + pub stroke_width: f64, +} + +impl ClickTarget { + /// Does the click target intersect the rectangle + pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool { + let quad = layer_transform.inverse() * document_quad; + + // Check if outlines intersect + if self + .subpath + .iter() + .any(|path_segment| quad.bezier_lines().any(|line| !path_segment.intersections(&line, None, None).is_empty())) + { + return true; + } + // Check if selection is entirely within the shape + if self.subpath.closed() && self.subpath.contains_point(quad.center()) { + return true; + } + + // Check if shape is entirely within selection + self.subpath + .manipulator_groups() + .first() + .map(|group| group.anchor) + .map(|shape_point| quad.contains(shape_point)) + .unwrap_or_default() + } + + /// Does the click target intersect the point (accounting for stroke size) + pub fn intersect_point(&self, point: DVec2, layer_transform: DAffine2) -> bool { + // Allows for selecting lines + // TODO: actual intersection of stroke + let inflated_quad = Quad::from_box([point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)]); + self.intersect_rectangle(inflated_quad, layer_transform) + } +} + /// Mutable state used whilst rendering to an SVG pub struct SvgRender { pub svg: SvgSegmentList, @@ -109,6 +154,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String { pub trait GraphicElementRendered { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>; + fn add_click_targets(&self, click_targets: &mut Vec); } impl GraphicElementRendered for GraphicGroup { @@ -118,6 +164,7 @@ impl GraphicElementRendered for GraphicGroup { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { self.iter().filter_map(|element| element.graphic_element_data.bounding_box(transform)).reduce(Quad::combine_bounds) } + fn add_click_targets(&self, _click_targets: &mut Vec) {} } impl GraphicElementRendered for VectorData { @@ -140,6 +187,14 @@ impl GraphicElementRendered for VectorData { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { self.bounding_box_with_transform(self.transform * transform) } + fn add_click_targets(&self, click_targets: &mut Vec) { + let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight); + let update_closed = |mut subpath: bezier_rs::Subpath| { + subpath.set_closed(self.style.fill().is_some()); + subpath + }; + click_targets.extend(self.subpaths.iter().cloned().map(update_closed).map(|subpath| ClickTarget { stroke_width, subpath })) + } } impl GraphicElementRendered for Artboard { @@ -195,7 +250,15 @@ impl GraphicElementRendered for Artboard { } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box(); - [self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds) + if self.clip { + Some(artboard_bounds) + } else { + [self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds) + } + } + fn add_click_targets(&self, click_targets: &mut Vec) { + let subpath = Subpath::new_rect(self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()); + click_targets.push(ClickTarget { stroke_width: 0., subpath }); } } @@ -216,6 +279,10 @@ impl GraphicElementRendered for ImageFrame { let transform = self.transform * transform; (transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) } + fn add_click_targets(&self, click_targets: &mut Vec) { + let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); + click_targets.push(ClickTarget { subpath, stroke_width: 0. }); + } } impl GraphicElementRendered for GraphicElementData { @@ -238,6 +305,16 @@ impl GraphicElementRendered for GraphicElementData { GraphicElementData::Artboard(artboard) => artboard.bounding_box(transform), } } + + fn add_click_targets(&self, click_targets: &mut Vec) { + match self { + GraphicElementData::VectorShape(vector_data) => vector_data.add_click_targets(click_targets), + GraphicElementData::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets), + GraphicElementData::Text(_) => todo!("click target for text GraphicElementData"), + GraphicElementData::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets), + GraphicElementData::Artboard(artboard) => artboard.add_click_targets(click_targets), + } + } } /// A segment of an svg string to allow for embedding blob urls diff --git a/node-graph/gcore/src/graphic_element/renderer/quad.rs b/node-graph/gcore/src/graphic_element/renderer/quad.rs index 0ce59b41f8..be44fc21d6 100644 --- a/node-graph/gcore/src/graphic_element/renderer/quad.rs +++ b/node-graph/gcore/src/graphic_element/renderer/quad.rs @@ -5,6 +5,11 @@ use glam::{DAffine2, DVec2}; pub struct Quad([DVec2; 4]); impl Quad { + /// Create a zero sized quad at the point + pub fn from_point(point: DVec2) -> Self { + Self([point; 4]) + } + /// Convert a box defined by two corner points to a quad. pub fn from_box(bbox: [DVec2; 2]) -> Self { let size = bbox[1] - bbox[0]; @@ -12,7 +17,7 @@ impl Quad { } /// Get all the edges in the quad. - pub fn lines_glam(&self) -> impl Iterator + '_ { + pub fn bezier_lines(&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)) @@ -40,6 +45,33 @@ impl Quad { pub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] { [a[0].min(b[0]), a[1].max(b[1])] } + + /// Expand a quad by a certain amount on all sides. + /// + /// Not currently very optimised + pub fn inflate(&self, offset: f64) -> Quad { + let offset = |index_before, index, index_after| { + let [point_before, point, point_after]: [DVec2; 3] = [self.0[index_before], self.0[index], self.0[index_after]]; + let [line_in, line_out] = [point - point_before, point_after - point]; + let angle = line_in.angle_between(-line_out); + let offset_length = offset / (std::f64::consts::FRAC_PI_2 - angle / 2.).cos(); + point + (line_in.perp().normalize_or_zero() + line_out.perp().normalize_or_zero()).normalize_or_zero() * offset_length + }; + Self([offset(3, 0, 1), offset(0, 1, 2), offset(1, 2, 3), offset(2, 3, 0)]) + } + + /// Does this quad contain a point + /// + /// Code from https://wrfranklin.org/Research/Short_Notes/pnpoly.html + pub fn contains(&self, p: DVec2) -> bool { + let mut inside = false; + for (i, j) in (0..4).zip([3, 0, 1, 2]) { + if (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < (self.0[j].x - self.0[i].x * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) { + inside = !inside; + } + } + inside + } } impl core::ops::Mul for DAffine2 { @@ -49,3 +81,26 @@ impl core::ops::Mul for DAffine2 { Quad(rhs.0.map(|point| self.transform_point2(point))) } } +#[test] +fn offset_quad() { + fn eq(a: Quad, b: Quad) -> bool { + a.0.iter().zip(b.0).all(|(a, b)| a.abs_diff_eq(b, 0.0001)) + } + + assert!(eq(Quad::from_box([DVec2::ZERO, DVec2::ONE]).inflate(0.5), Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)]))); + assert!(eq(Quad::from_box([DVec2::ONE, DVec2::ZERO]).inflate(0.5), Quad::from_box([DVec2::splat(1.5), DVec2::splat(-0.5)]))); + assert!(eq( + (DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).inflate(0.5), + DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)]) + )); +} +#[test] +fn quad_contains() { + assert!(Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::splat(0.5))); + assert!(Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::splat(0.5))); + assert!((DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::new(-0.5, 0.5))); + + assert!(!Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::new(1., 1.1))); + assert!(!Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::new(0.5, -0.01))); + assert!(!(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::splat(0.5))); +} diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index b99955e16f..9e7ced23a6 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -5,6 +5,7 @@ use glam::DVec2; use crate::raster::ImageFrame; use crate::raster::Pixel; use crate::vector::VectorData; +use crate::GraphicElementData; use crate::Node; pub trait Transform { @@ -42,6 +43,52 @@ impl TransformMut for ImageFrame

{ &mut self.transform } } +impl Transform for GraphicElementData { + fn transform(&self) -> DAffine2 { + match self { + GraphicElementData::VectorShape(vector_shape) => vector_shape.transform(), + GraphicElementData::ImageFrame(image_frame) => image_frame.transform(), + GraphicElementData::Text(_) => todo!("Transform of text"), + GraphicElementData::GraphicGroup(_graphic_group) => DAffine2::IDENTITY, + GraphicElementData::Artboard(_artboard) => DAffine2::IDENTITY, + } + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + match self { + GraphicElementData::VectorShape(vector_shape) => vector_shape.local_pivot(pivot), + GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot), + GraphicElementData::Text(_) => todo!("Transform of text"), + GraphicElementData::GraphicGroup(_graphic_group) => pivot, + GraphicElementData::Artboard(_artboard) => pivot, + } + } + fn decompose_scale(&self) -> DVec2 { + let standard = || { + DVec2::new( + self.transform().transform_vector2((1., 0.).into()).length(), + self.transform().transform_vector2((0., 1.).into()).length(), + ) + }; + match self { + GraphicElementData::VectorShape(vector_shape) => vector_shape.decompose_scale(), + GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(), + GraphicElementData::Text(_) => todo!("Transform of text"), + GraphicElementData::GraphicGroup(_graphic_group) => standard(), + GraphicElementData::Artboard(_artboard) => standard(), + } + } +} +impl TransformMut for GraphicElementData { + fn transform_mut(&mut self) -> &mut DAffine2 { + match self { + GraphicElementData::VectorShape(vector_shape) => vector_shape.transform_mut(), + GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(), + GraphicElementData::Text(_) => todo!("Transform of text"), + GraphicElementData::GraphicGroup(_graphic_group) => todo!("Mutable transform of graphic group"), + GraphicElementData::Artboard(_artboard) => todo!("Mutable transform of artboard"), + } + } +} impl Transform for VectorData { fn transform(&self) -> DAffine2 { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index a40742f47c..2ddec7e226 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -453,9 +453,7 @@ impl NodeNetwork { return true; } // Get the outputs - let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::>>() else { - return false; - }; + let mut stack = self.outputs.iter().filter_map(|&output| self.nodes.get(&output.node_id)).collect::>(); let mut already_visited = HashSet::new(); already_visited.extend(self.outputs.iter().map(|output| output.node_id)); diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index 244f6c1a22..6be6fafb20 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -328,7 +328,7 @@ async fn create_compute_pass_descriptor( #[cfg(feature = "quantization")] buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()], #[cfg(not(feature = "quantization"))] - buffers: vec![width_uniform.clone(), storage_buffer.clone()], + buffers: vec![width_uniform, storage_buffer], }; let shader = gpu_executor::Shader { @@ -343,13 +343,13 @@ async fn create_compute_pass_descriptor( shader: shader.into(), entry_point: "eval".to_string(), bind_group: bind_group.into(), - output_buffer: output_buffer.clone(), + output_buffer, }; log::debug!("created pipeline"); Ok(ComputePass { pipeline_layout: pipeline, - readback_buffer: Some(readback_buffer.clone()), + readback_buffer: Some(readback_buffer), }) } /* diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 2b98b02705..a7adc61eeb 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -318,7 +318,7 @@ fn node_registry() -> HashMap, input: DAffine2, params: [Color]), register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame, params: []), - register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []), + register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicElementData, params: []), async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]), register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []), async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc::Surface>>, params: []), @@ -653,10 +653,11 @@ fn node_registry() -> HashMap, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []), - register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::vector::VectorData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), - register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), - register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), - register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), + register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicElementData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), + register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []), + register_node!(graphene_core::ToGraphicElementData, input: ImageFrame, params: []), + register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []), + register_node!(graphene_core::ToGraphicElementData, input: graphene_core::Artboard, params: []), register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]), ]; let mut map: HashMap> = HashMap::new(); diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index a1745aa835..486c6ff72b 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -4,7 +4,6 @@ use syn::{Path, PathArguments, PathSegment, Token}; /// Returns `Ok(Vec)` if all items are `Ok(T)`, else returns a combination of every error encountered (not just the first one) pub fn fold_error_iter(iter: impl Iterator>) -> syn::Result> { - #[allow(clippy::manual_try_fold)] iter.fold(Ok(vec![]), |acc, x| match acc { Ok(mut v) => x.map(|x| { v.push(x);