diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs index db807fda4d..7ffc4bd6ed 100644 --- a/document-legacy/src/document_metadata.rs +++ b/document-legacy/src/document_metadata.rs @@ -3,15 +3,17 @@ use graphene_core::renderer::ClickTarget; use std::collections::HashMap; use std::num::NonZeroU64; -use graph_craft::document::{NodeId, NodeNetwork}; +use graph_craft::document::{DocumentNode, NodeId, NodeNetwork}; use graphene_core::renderer::Quad; #[derive(Debug, Clone)] pub struct DocumentMetadata { transforms: HashMap, + upstream_transforms: HashMap, structure: HashMap, click_targets: HashMap>, + selected_nodes: Vec, /// Transform from document space to viewport space. pub document_to_viewport: DAffine2, } @@ -20,13 +22,17 @@ impl Default for DocumentMetadata { fn default() -> Self { Self { transforms: HashMap::new(), + upstream_transforms: HashMap::new(), click_targets: HashMap::new(), structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]), + selected_nodes: Vec::new(), document_to_viewport: DAffine2::IDENTITY, } } } +pub struct SelectionChanged; +// layer iters impl DocumentMetadata { /// Get the root layer from the document pub const fn root(&self) -> LayerNodeIdentifier { @@ -38,7 +44,7 @@ impl DocumentMetadata { } pub fn selected_layers(&self) -> impl Iterator + '_ { - self.all_layers() + self.all_layers().filter(|layer| self.selected_nodes.contains(&layer.to_node())) } pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool { @@ -46,7 +52,19 @@ impl DocumentMetadata { } pub fn selected_visible_layers(&self) -> impl Iterator + '_ { - self.all_layers() + self.selected_layers() + } + + pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> { + self.selected_nodes.iter() + } + + pub fn selected_nodes_ref(&self) -> &Vec { + &self.selected_nodes + } + + pub fn has_selected_nodes(&self) -> bool { + !self.selected_nodes.is_empty() } /// Access the [`NodeRelations`] of a layer @@ -59,35 +77,125 @@ impl DocumentMetadata { 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; + pub fn shallowest_unique_layers<'a>(&self, layers: impl Iterator) -> Vec> { + let mut sorted_layers = layers + .map(|layer| { + let mut layer_path = layer.ancestors(self).collect::>(); + layer_path.reverse(); + layer_path + }) + .collect::>(); + sorted_layers.sort(); + // Sorting here creates groups of similar UUID paths + sorted_layers.dedup_by(|a, b| a.starts_with(b)); + sorted_layers + } +} + +// selected layer modifications +impl DocumentMetadata { + #[must_use] + pub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) -> SelectionChanged { + self.selected_nodes.retain(f); + SelectionChanged + } + #[must_use] + pub fn set_selected_nodes(&mut self, new: Vec) -> SelectionChanged { + self.selected_nodes = new; + SelectionChanged + } + #[must_use] + pub fn add_selected_nodes(&mut self, iter: impl IntoIterator) -> SelectionChanged { + self.selected_nodes.extend(iter); + SelectionChanged + } + #[must_use] + pub fn clear_selected_nodes(&mut self) -> SelectionChanged { + self.set_selected_nodes(Vec::new()) } + /// Loads the structure of layer nodes from a node graph. + pub fn load_structure(&mut self, graph: &NodeNetwork) { + self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]); + + let id = graph.outputs[0].node_id; + let Some(output_node) = graph.nodes.get(&id) else { + return; + }; + let Some((layer_node, node_id)) = first_child_layer(graph, output_node, id) else { + return; + }; + let parent = LayerNodeIdentifier::ROOT; + let mut stack = vec![(layer_node, node_id, parent)]; + while let Some((node, id, parent)) = stack.pop() { + let mut current = Some((node, id)); + while let Some(&(current_node, current_id)) = current.as_ref() { + let current_identifier = LayerNodeIdentifier::new_unchecked(current_id); + if !self.structure.contains_key(¤t_identifier) { + parent.push_child(self, current_identifier); + + if let Some((child_node, child_id)) = first_child_layer(graph, current_node, current_id) { + stack.push((child_node, child_id, current_identifier)); + } + } + + current = sibling_below(graph, current_node, current_id); + } + } + } +} + +fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> { + graph.primary_flow_from_opt(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer") +} +fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> { + node.inputs[7].as_node().and_then(|id| graph.nodes.get(&id).filter(|node| node.name == "Layer").map(|node| (node, id))) +} + +// transforms +impl DocumentMetadata { + /// Update the cached transforms of the layers + pub fn update_transforms(&mut self, new_transforms: HashMap, new_upstream_transforms: HashMap) { + self.transforms = new_transforms; + self.upstream_transforms = new_upstream_transforms; + } + + /// Access the cached transformation to document space from layer space + pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 { + self.transforms.get(&layer).copied().unwrap_or_else(|| { + warn!("Tried to access transform of bad layer {layer:?}"); + DAffine2::IDENTITY + }) + } + + pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { + self.document_to_viewport * self.transform_to_document(layer) + } + + pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 { + self.upstream_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY) + } +} + +fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool { + network.primary_flow_from_opt(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard") +} + +// click targets +impl DocumentMetadata { /// 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 viewport space quad - pub fn intersect_quad(&self, viewport_quad: Quad) -> Option { + pub fn intersect_quad<'a>(&'a self, viewport_quad: Quad, network: &'a NodeNetwork) -> impl Iterator + 'a { let document_quad = self.document_to_viewport.inverse() * viewport_quad; self.root() .decendants(self) + .filter(|&layer| !is_artboard(layer, network)) .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)))) + .filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.transform_to_document(*layer)))) .map(|(layer, _)| layer) } @@ -97,13 +205,13 @@ impl DocumentMetadata { self.root() .decendants(self) .filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets))) - .filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer)))) + .filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_to_document(*layer)))) .map(|(layer, _)| layer) } /// Find the layer that has been clicked on from a viewport space location - pub fn click(&self, viewport_location: DVec2) -> Option { - self.click_xray(viewport_location).next() + pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option { + self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).next() } /// Get the bounding box of the click target of the specified layer in the specified transform space @@ -115,14 +223,47 @@ impl DocumentMetadata { .reduce(Quad::combine_bounds) } + /// Calculate the corners of the bounding box but with a nonzero size. + /// + /// If the layer bounds are `0` in either axis then they are changed to be `1`. + pub fn nonzero_bounding_box(&self, layer: LayerNodeIdentifier) -> [DVec2; 2] { + let [bounds_min, mut bounds_max] = self.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap_or_default(); + + let bounds_size = bounds_max - bounds_min; + if bounds_size.x < 1e-10 { + bounds_max.x = bounds_min.x + 1.; + } + if bounds_size.y < 1e-10 { + bounds_max.y = bounds_min.y + 1.; + } + + [bounds_min, bounds_max] + } + /// Get the bounding box of the click target of the specified layer in document space pub fn bounding_box_document(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> { - self.bounding_box_with_transform(layer, self.transform_from_document(layer)) + self.bounding_box_with_transform(layer, self.transform_to_document(layer)) } /// Get the bounding box of the click target of the specified layer in viewport space pub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> { - self.bounding_box_with_transform(layer, self.transform_from_viewport(layer)) + self.bounding_box_with_transform(layer, self.transform_to_viewport(layer)) + } + + pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { + self.selected_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds) + } + + /// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable)) + pub fn document_bounds(&self) -> Option<[DVec2; 2]> { + self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds) + } + + pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath { + let Some(click_targets) = self.click_targets.get(&layer) else { + return graphene_core::vector::Subpath::new(); + }; + graphene_core::vector::Subpath::from_bezier_rs(click_targets.iter().map(|click_target| &click_target.subpath)) } } @@ -242,7 +383,7 @@ impl LayerNodeIdentifier { pub fn decendants(self, document_metadata: &DocumentMetadata) -> DecendantsIter { DecendantsIter { front: self.first_child(document_metadata), - back: self.last_child(document_metadata), + back: self.last_child(document_metadata).and_then(|child| child.last_children(document_metadata).last()), document_metadata, } } @@ -339,6 +480,17 @@ impl LayerNodeIdentifier { pub fn exists(&self, document_metadata: &DocumentMetadata) -> bool { document_metadata.get_relations(*self).is_some() } + + pub fn starts_with(&self, other: Self, document_metadata: &DocumentMetadata) -> bool { + self.ancestors(document_metadata).any(|parent| parent == other) + } + + pub fn child_of_root(&self, document_metadata: &DocumentMetadata) -> Self { + self.ancestors(document_metadata) + .filter(|&layer| layer != LayerNodeIdentifier::ROOT) + .last() + .expect("There should be a layer before the root") + } } impl From for LayerNodeIdentifier { @@ -457,7 +609,8 @@ fn test_tree() { 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); + LayerNodeIdentifier::new_unchecked(9).push_child(document_metadata, LayerNodeIdentifier::new_unchecked(10)); 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]); + assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::>(), vec![2, 3, 4, 5, 9, 10]); + assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::>(), vec![10, 9, 5, 4, 3, 2]); } diff --git a/document-legacy/src/layers/shape_layer.rs b/document-legacy/src/layers/shape_layer.rs index 43236f7c37..b222362686 100644 --- a/document-legacy/src/layers/shape_layer.rs +++ b/document-legacy/src/layers/shape_layer.rs @@ -32,11 +32,12 @@ impl LayerData for ShapeLayer { let layer_bounds = subpath.bounding_box().unwrap_or_default(); let transform = self.transform(transforms, render_data.view_mode); - let inverse = transform.inverse(); - if !inverse.is_finite() { + if !transform.is_finite() || transform.matrix2.determinant() == 0. { let _ = write!(svg, ""); return false; } + let inverse = transform.inverse(); + subpath.apply_affine(transform); let transformed_bounds = subpath.bounding_box().unwrap_or_default(); diff --git a/document-legacy/src/lib.rs b/document-legacy/src/lib.rs index dcb8e9c903..5a76b65ffa 100644 --- a/document-legacy/src/lib.rs +++ b/document-legacy/src/lib.rs @@ -3,13 +3,10 @@ extern crate log; 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. pub mod intersection; pub mod layers; pub mod operation; diff --git a/document-legacy/src/response.rs b/document-legacy/src/response.rs index 0f187f41a3..05c901277e 100644 --- a/document-legacy/src/response.rs +++ b/document-legacy/src/response.rs @@ -11,9 +11,6 @@ pub enum DocumentResponse { FolderChanged { path: Vec, }, - AddSelectedLayer { - additional_layers: Vec>, - }, CreatedLayer { path: Vec, is_selected: bool, @@ -38,7 +35,6 @@ impl fmt::Display for DocumentResponse { match self { DocumentResponse::DocumentChanged { .. } => write!(f, "DocumentChanged"), DocumentResponse::FolderChanged { .. } => write!(f, "FolderChanged"), - DocumentResponse::AddSelectedLayer { .. } => write!(f, "AddSelectedLayer"), DocumentResponse::CreatedLayer { .. } => write!(f, "CreatedLayer"), DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"), DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"), diff --git a/editor/Cargo.toml b/editor/Cargo.toml index a58cc3886d..d0ca4a1b1b 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } graphite-proc-macros = { path = "../proc-macros" } bezier-rs = { path = "../libraries/bezier-rs" } -glam = { version = "0.24", features = ["serde"] } +glam = { version = "0.24", features = ["serde", "debug-glam-assert"] } remain = "0.2.2" derivative = "2.2.0" once_cell = "1.13.0" # Remove when `core::cell::LazyCell` is stabilized () diff --git a/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs b/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs index 21d80c0d29..0b5b729a54 100644 --- a/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs @@ -1,4 +1,5 @@ use crate::messages::layout::utility_types::widget_prelude::*; +use crate::messages::prelude::*; use graphite_proc_macros::WidgetBuilder; use derivative::*; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 59974dd6a0..2f26a8722b 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -28,7 +28,6 @@ use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as Doc use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeInput, NodeNetwork}; use graphene_core::raster::ImageFrame; -use graphene_core::renderer::Quad; use graphene_core::text::Font; use glam::{DAffine2, DVec2}; @@ -123,9 +122,6 @@ impl MessageHandler responses.add(FolderChanged { affected_folder_path: path.clone() }), - DocumentResponse::AddSelectedLayer { additional_layers } => responses.add(AddSelectedLayers { - additional_layers: additional_layers.clone(), - }), DocumentResponse::DeletedLayer { path } => { self.layer_metadata.remove(path); } @@ -139,20 +135,8 @@ impl MessageHandler { - if self.layer_metadata.contains_key(path) { - warn!("CreatedLayer overrides existing layer metadata."); - } - self.layer_metadata.insert(path.clone(), LayerMetadata::new(false)); - - responses.add(LayerChanged { affected_layer_path: path.clone() }); - self.layer_range_selection_reference = path.clone(); - - if *is_selected { - responses.add(AddSelectedLayers { - additional_layers: vec![path.clone()], - }); - } + DocumentResponse::CreatedLayer { .. } => { + unimplemented!("We should no longer be creating layers in the document and should instead be using the node graph.") } DocumentResponse::DocumentChanged => responses.add(RenderDocument), DocumentResponse::DeletedSelectedManipulatorPoints => { @@ -176,11 +160,11 @@ impl MessageHandler { - let document_bounds = self.document_bounds(); + let document_bounds = self.metadata().document_bounds(); self.navigation_handler.process_message( message, responses, - (&self.document_legacy, document_bounds, ipp, self.selected_visible_layers_bounding_box(&render_data)), + (&self.document_legacy, document_bounds, ipp, self.metadata().selected_visible_layers_bounding_box_viewport()), ); } #[remain::unsorted] @@ -235,39 +219,38 @@ impl MessageHandler { self.backup(responses); - let (paths, boxes): (Vec<_>, Vec<_>) = self - .selected_layers() - .filter_map(|path| self.document_legacy.viewport_bounding_box(path, &render_data).ok()?.map(|b| (path, b))) - .unzip(); let axis = match axis { AlignAxis::X => DVec2::X, AlignAxis::Y => DVec2::Y, }; - let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5); - if let Some(combined_box) = self.document_legacy.combined_viewport_bounding_box(self.selected_layers(), &render_data) { - let aggregated = match aggregate { - AlignAggregate::Min => combined_box[0], - AlignAggregate::Max => combined_box[1], - AlignAggregate::Center => lerp(&combined_box), - AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(), + let Some(combined_box) = self.metadata().selected_visible_layers_bounding_box_viewport() else { + return; + }; + + let aggregated = match aggregate { + AlignAggregate::Min => combined_box[0], + AlignAggregate::Max => combined_box[1], + AlignAggregate::Center => (combined_box[0] + combined_box[1]) / 2., + }; + for layer in self.metadata().selected_layers() { + let Some(bbox) = self.metadata().bounding_box_viewport(layer) else { + continue; }; - for (path, bbox) in paths.into_iter().zip(boxes) { - let center = match aggregate { - AlignAggregate::Min => bbox[0], - AlignAggregate::Max => bbox[1], - _ => lerp(&bbox), - }; - let translation = (aggregated - center) * axis; - responses.add(GraphOperationMessage::TransformChange { - layer: path.to_vec(), - transform: DAffine2::from_translation(translation), - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); - } - responses.add(BroadcastEvent::DocumentIsDirty); + let center = match aggregate { + AlignAggregate::Min => bbox[0], + AlignAggregate::Max => bbox[1], + _ => (bbox[0] + bbox[1]) / 2., + }; + let translation = (aggregated - center) * axis; + responses.add(GraphOperationMessage::TransformChange { + layer: layer.to_path(), + transform: DAffine2::from_translation(translation), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); } + responses.add(BroadcastEvent::DocumentIsDirty); } BackupDocument { document, layer_metadata } => self.backup_with_document(document, layer_metadata, responses), ClearLayerTree => { @@ -374,8 +357,8 @@ impl MessageHandler self.all_layer_bounds(&render_data), - ExportBounds::Selection => self.selected_visible_layers_bounding_box(&render_data), - ExportBounds::Artboard(id) => self.document_legacy.metadata.bounding_box_document(id), + ExportBounds::Selection => self.metadata().selected_visible_layers_bounding_box_viewport(), + ExportBounds::Artboard(id) => self.metadata().bounding_box_document(id), } .unwrap_or_default(); let size = bounds[1] - bounds[0]; @@ -405,12 +388,12 @@ impl MessageHandler DVec2::new(-1., 1.), FlipAxis::Y => DVec2::new(1., -1.), }; - if let Some([min, max]) = self.document_legacy.combined_viewport_bounding_box(self.selected_layers(), &render_data) { + if let Some([min, max]) = self.metadata().selected_visible_layers_bounding_box_viewport() { let center = (max + min) / 2.; let bbox_trans = DAffine2::from_translation(-center); - for path in self.selected_layers() { + for layer in self.metadata().selected_layers() { responses.add(GraphOperationMessage::TransformChange { - layer: path.to_vec(), + layer: layer.to_path(), transform: DAffine2::from_scale(scale), transform_in: TransformIn::Scope { scope: bbox_trans }, skip_rerender: false, @@ -636,7 +619,7 @@ impl MessageHandler { let document_transform_scale = self.navigation_handler.snapped_scale(); - let ruler_origin = self.document_legacy.metadata.document_to_viewport.transform_point2(DVec2::ZERO); + let ruler_origin = self.metadata().document_to_viewport.transform_point2(DVec2::ZERO); let log = document_transform_scale.log2(); let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) }; let ruler_spacing = ruler_interval * document_transform_scale; @@ -654,7 +637,7 @@ impl MessageHandler { - if let Some(bounds) = self.document_bounds() { + if let Some(bounds) = self.metadata().document_bounds() { responses.add(NavigationMessage::FitViewportToBounds { bounds, padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR), @@ -964,10 +947,13 @@ impl DocumentMessageHandler { pub fn network(&self) -> &NodeNetwork { &self.document_legacy.document_network } + pub fn metadata(&self) -> &document_legacy::document_metadata::DocumentMetadata { + &self.document_legacy.metadata + } /// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end pub(crate) fn remove_document_transform(&mut self) -> DAffine2 { - let old_artwork_transform = self.document_legacy.metadata.document_to_viewport; + let old_artwork_transform = self.metadata().document_to_viewport; self.document_legacy.metadata.document_to_viewport = DAffine2::IDENTITY; DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root); @@ -1074,11 +1060,6 @@ impl DocumentMessageHandler { } } - pub fn selected_visible_layers_bounding_box(&self, render_data: &RenderData) -> Option<[DVec2; 2]> { - let paths = self.selected_visible_layers(); - self.document_legacy.combined_viewport_bounding_box(paths, render_data) - } - pub fn selected_layers(&self) -> impl Iterator { self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())) } @@ -1130,17 +1111,18 @@ impl DocumentMessageHandler { fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec, data: &mut Vec, path: &mut Vec) { let mut space = 0; - for layer_node in folder.children(&self.document_legacy.metadata) { + for layer_node in folder.children(&self.metadata()) { data.push(layer_node.to_node()); info!("Pushed child"); space += 1; - if layer_node.has_children(&self.document_legacy.metadata) { + if layer_node.has_children(&self.metadata()) { path.push(layer_node.to_node()); - if self.layer_metadata(path).expanded { - structure.push(space); - self.serialize_structure(folder, structure, data, path); - space = 0; - } + + // TODO: Skip if folder is not expanded. + structure.push(space); + self.serialize_structure(layer_node, structure, data, path); + space = 0; + path.pop(); } } @@ -1181,7 +1163,7 @@ impl DocumentMessageHandler { /// ``` pub fn serialize_root(&self) -> Vec { let (mut structure, mut data) = (vec![0], Vec::new()); - self.serialize_structure(self.document_legacy.metadata.root(), &mut structure, &mut data, &mut vec![]); + self.serialize_structure(self.metadata().root(), &mut structure, &mut data, &mut vec![]); structure[0] = structure.len() as u64 - 1; structure.extend(data); @@ -1279,7 +1261,7 @@ impl DocumentMessageHandler { /// Replace the document with a new document save, returning the document save. pub fn replace_document(&mut self, DocumentSave { document, layer_metadata }: DocumentSave) -> DocumentSave { // Keeping the root is required if the bounds of the viewport have changed during the operation - let old_root = self.document_legacy.metadata.document_to_viewport; + let old_root = self.metadata().document_to_viewport; let document = std::mem::replace(&mut self.document_legacy, document); self.document_legacy.metadata.document_to_viewport = old_root; self.document_legacy.root.cache_dirty = true; @@ -1414,10 +1396,7 @@ impl DocumentMessageHandler { pub fn layer_panel_entry_from_path(&self, path: &[LayerId], render_data: &RenderData) -> Option { let layer_metadata = self.layer_metadata(path); - let transform = self - .document_legacy - .generate_transform_across_scope(path, Some(self.document_legacy.metadata.document_to_viewport.inverse())) - .ok()?; + let transform = self.document_legacy.generate_transform_across_scope(path, Some(self.metadata().document_to_viewport.inverse())).ok()?; let layer = self.document_legacy.layer(path).ok()?; Some(LayerPanelEntry::new(layer_metadata, transform, layer, path.to_vec(), render_data)) @@ -1439,15 +1418,6 @@ impl DocumentMessageHandler { self.document_legacy.viewport_bounding_box(&[], render_data).ok().flatten() } - /// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable)) - pub fn document_bounds(&self) -> Option<[DVec2; 2]> { - self.document_legacy - .metadata - .all_layers() - .filter_map(|layer| self.document_legacy.metadata.bounding_box_viewport(layer)) - .reduce(Quad::combine_bounds) - } - /// Calculate the path that new layers should be inserted to. /// Depends on the selected layers as well as their types (Folder/Non-Folder) pub fn get_path_for_new_layer(&self) -> Vec { 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 cfb9037a18..3ca46a0473 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 @@ -59,9 +59,9 @@ impl<'a> ModifyInputsContext<'a> { } /// Updates the input of an existing node - fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec)) { + fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec, NodeId, &DocumentMetadata)) { let document_node = self.network.nodes.get_mut(&node_id).unwrap(); - update_input(&mut document_node.inputs); + update_input(&mut document_node.inputs, node_id, &self.document_metadata); } pub fn insert_between(&mut self, id: NodeId, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize, shift_upstream: IVec2) -> Option { @@ -216,7 +216,7 @@ impl<'a> ModifyInputsContext<'a> { } /// Inserts a new node and modifies the inputs - fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec)) { + fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec, NodeId, &DocumentMetadata)) { let output_node_id = self.layer_node.unwrap_or(self.network.outputs[0].node_id); let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else { warn!("Output node doesn't exist"); @@ -235,19 +235,20 @@ impl<'a> ModifyInputsContext<'a> { return; }; let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata); - update_input(&mut new_document_node.inputs); + update_input(&mut new_document_node.inputs, node_id, &self.document_metadata); self.network.nodes.insert(node_id, new_document_node); } /// Changes the inputs of a specific node - fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec)) { + fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec, NodeId, &DocumentMetadata)) { let existing_node_id = self.network.primary_flow_from_opt(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id); if let Some(node_id) = existing_node_id { self.modify_existing_node_inputs(node_id, update_input); } else { self.modify_new_node(name, update_input); } - self.node_graph.nested_path.clear(); + + self.node_graph.network.clear(); self.responses.add(PropertiesPanelMessage::ResendActiveProperties); let layer_path = self.layer.to_vec(); @@ -261,8 +262,25 @@ impl<'a> ModifyInputsContext<'a> { } } + /// Changes the inputs of a all of the existing instances of a node name + fn modify_all_node_inputs(&mut self, name: &'static str, skip_rerender: bool, mut update_input: impl FnMut(&mut Vec, NodeId, &DocumentMetadata)) { + let existing_nodes: Vec<_> = self.network.primary_flow_from_opt(self.layer_node).filter(|(node, _)| node.name == name).map(|(_, id)| id).collect(); + for existing_node_id in existing_nodes { + self.modify_existing_node_inputs(existing_node_id, &mut update_input); + } + + self.responses.add(PropertiesPanelMessage::ResendActiveProperties); + let layer_path = self.layer.to_vec(); + + if !skip_rerender { + self.responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + self.responses.add(DocumentMessage::FrameClear); + } + } + fn fill_set(&mut self, fill: Fill) { - self.modify_inputs("Fill", false, |inputs| { + self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| { let fill_type = match fill { Fill::None => FillType::None, Fill::Solid(_) => FillType::Solid, @@ -284,7 +302,7 @@ impl<'a> ModifyInputsContext<'a> { } fn stroke_set(&mut self, stroke: Stroke) { - self.modify_inputs("Stroke", false, |inputs| { + self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| { inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false); inputs[2] = NodeInput::value(TaggedValue::F32(stroke.weight as f32), false); inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false); @@ -296,49 +314,45 @@ impl<'a> ModifyInputsContext<'a> { } fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) { - self.modify_inputs("Transform", skip_rerender, |inputs| { + self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| { let layer_transform = transform_utils::get_current_transform(inputs); + let upstream_transform = metadata.upstream_transform(node_id); let to = match transform_in { TransformIn::Local => DAffine2::IDENTITY, TransformIn::Scope { scope } => scope * parent_transform, TransformIn::Viewport => parent_transform, }; - let pivot = DAffine2::from_translation(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))); + let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)))); let transform = pivot.inverse() * to.inverse() * transform * to * pivot * layer_transform; transform_utils::update_transform(inputs, transform); }); } fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option, bounds: LayerBounds, skip_rerender: bool) { - self.modify_inputs("Transform", skip_rerender, |inputs| { - let current_transform_node = transform_utils::get_current_transform(inputs); + self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| { + let upstream_transform = metadata.upstream_transform(node_id); let to = match transform_in { TransformIn::Local => DAffine2::IDENTITY, TransformIn::Scope { scope } => scope * parent_transform, TransformIn::Viewport => parent_transform, }; - let pivot = DAffine2::from_translation(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))); + let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)))); - if let Some(current_transform) = current_transform.filter(|transform| transform.inverse().is_finite() && current_transform_node.inverse().is_finite()) { - // this_transform * upstream_transforms = current_transform - // So this_transform.inverse() * current_transform = upstream_transforms - let upstream_transform = (pivot * current_transform_node * pivot.inverse()).inverse() * current_transform; - // desired_final_transform = this_transform * upstream_transform - // So this_transform = desired_final_transform * upstream_transform.inverse() + if let Some(current_transform) = current_transform.filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.) { transform = transform * upstream_transform.inverse(); } - - let transform = pivot.inverse() * to.inverse() * transform * pivot; - transform_utils::update_transform(inputs, transform); + let final_transform = pivot.inverse() * to.inverse() * transform * pivot; + transform_utils::update_transform(inputs, final_transform); }); } fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) { - self.modify_inputs("Transform", false, |inputs| { + self.modify_inputs("Transform", false, |inputs, node_id, metadata| { let layer_transform = transform_utils::get_current_transform(inputs); - let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs))); - let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot)); + let upstream_transform = metadata.upstream_transform(node_id); + let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs)))); + let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(new_pivot))); let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform; transform_utils::update_transform(inputs, transform); inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false); @@ -346,14 +360,15 @@ impl<'a> ModifyInputsContext<'a> { } fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) { - self.modify_inputs("Transform", false, |inputs| { + self.modify_all_node_inputs("Transform", false, |inputs, node_id, metadata| { + let upstream_transform = metadata.upstream_transform(node_id); let layer_transform = transform_utils::get_current_transform(inputs); let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs); let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalized_pivot + old_bounds_min; let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalized_pivot + new_bounds_min; - let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot); - let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot); + let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(new_layerspace_pivot)); + let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(old_layerspace_pivot)); let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform; transform_utils::update_transform(inputs, transform); @@ -369,7 +384,7 @@ impl<'a> ModifyInputsContext<'a> { let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE]; let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE]; - self.modify_inputs("Shape", false, |inputs| { + self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| { let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else { panic!("Shape does not have subpath and mirror angle inputs"); }; @@ -400,13 +415,13 @@ impl<'a> ModifyInputsContext<'a> { } fn brush_modify(&mut self, strokes: Vec) { - self.modify_inputs("Brush", false, |inputs| { + self.modify_inputs("Brush", false, |inputs, _node_id, _metadata| { inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false); }); } fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) { - self.modify_inputs("Artboard", false, |inputs| { + self.modify_inputs("Artboard", false, |inputs, _node_id, _metadata| { inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false); inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false); }); @@ -443,9 +458,10 @@ impl<'a> ModifyInputsContext<'a> { } } - for node_id in delete_nodes { - self.network.nodes.remove(&node_id); + for node_id in &delete_nodes { + self.network.nodes.remove(node_id); } + self.responses.add(self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id))); self.responses.add(DocumentMessage::DocumentStructureChanged); self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); @@ -503,29 +519,18 @@ impl MessageHandler { 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 current_transform = Some(document.metadata.transform_to_viewport(LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network))); let bounds = LayerBounds::new(document, &layer); 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(); - responses.add(match transform_in { - TransformIn::Local => Operation::SetLayerTransform { path: layer, transform }, - TransformIn::Scope { scope } => { - let scope = scope.to_cols_array(); - Operation::SetLayerTransformInScope { path: layer, transform, scope } - } - TransformIn::Viewport => Operation::SetLayerTransformInViewport { path: layer, transform }, - }); } GraphOperationMessage::TransformSetPivot { layer, pivot } => { let bounds = LayerBounds::new(document, &layer); if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { modify_inputs.pivot_set(pivot, bounds); } - - let pivot = pivot.into(); - responses.add(Operation::SetPivot { layer_path: layer, pivot }); } GraphOperationMessage::Vector { layer, modification } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs index fcd7874298..745922445a 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler/transform_utils.rs @@ -2,6 +2,7 @@ use crate::messages::portfolio::document::node_graph::VectorDataModification; use bezier_rs::{ManipulatorGroup, Subpath}; use document_legacy::document::Document; +use document_legacy::document_metadata::LayerNodeIdentifier; use graph_craft::document::{value::TaggedValue, NodeInput}; use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::{ManipulatorPointId, SelectedType}; @@ -52,18 +53,12 @@ pub struct LayerBounds { impl LayerBounds { /// Extract the layer bounds and their transform for a layer. - pub fn new(document: &Document, layer_path: &[u64]) -> Self { - let layer = document.layer(layer_path).ok(); - let bounds = layer - .and_then(|layer| layer.as_layer().ok()) - .and_then(|frame| frame.as_vector_data().as_ref().map(|vector| vector.nonzero_bounding_box())) - .unwrap_or([DVec2::ZERO, DVec2::ONE]); - let bounds_transform = DAffine2::IDENTITY; - let layer_transform = document.multiply_transforms(layer_path).unwrap_or_default(); + pub fn new(document: &Document, layer: &[u64]) -> Self { + let layer = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network); Self { - bounds, - bounds_transform, - layer_transform, + bounds: document.metadata.nonzero_bounding_box(layer), + bounds_transform: DAffine2::IDENTITY, + layer_transform: document.metadata.transform_to_document(layer), } } @@ -126,7 +121,7 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 { if let NodeInput::Value { tagged_value: TaggedValue::DVec2(pivot), .. - } = inputs[4] + } = inputs[5] { pivot } else { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index b442a67246..a62cb8d26d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -4,11 +4,15 @@ use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; -#[remain::sorted] #[impl_message(Message, DocumentMessage, NodeGraph)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum NodeGraphMessage { // Messages + Init, + AddSelectNodes { + nodes: Vec, + }, + SelectedNodesUpdated, CloseNodeGraph, ConnectNodesByLink { output_node: u64, @@ -63,9 +67,6 @@ pub enum NodeGraphMessage { serialized_nodes: String, }, RunDocumentGraph, - SelectNodes { - nodes: Vec, - }, SendGraph { should_rerender: bool, }, @@ -85,6 +86,9 @@ pub enum NodeGraphMessage { input_index: usize, value: TaggedValue, }, + SetSelectNodes { + nodes: Vec, + }, ShiftNode { node_id: NodeId, }, 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 805f9bcbf8..a6cda71f35 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 @@ -119,39 +119,19 @@ impl FrontendNodeType { #[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] pub struct NodeGraphMessageHandler { pub layer_path: Option>, - pub nested_path: Vec, - pub selected_nodes: Vec, + pub network: Vec, + has_selection: bool, #[serde(skip)] pub widgets: [LayoutGroup; 2], } +impl Into for document_legacy::document_metadata::SelectionChanged { + fn into(self) -> Message { + BroadcastMessage::TriggerEvent(BroadcastEvent::SelectionChanged).into() + } +} + impl NodeGraphMessageHandler { - fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork { - self.layer_path - .as_ref() - .and_then(|path| document.root.child(path)) - .and_then(|layer| layer.as_layer_network().ok()) - .unwrap_or(&document.document_network) - } - - fn get_root_network_mut<'a>(&self, document: &'a mut Document) -> &'a mut graph_craft::document::NodeNetwork { - self.layer_path - .as_ref() - .and_then(|path| document.root.child_mut(path)) - .and_then(|layer| layer.as_layer_network_mut().ok()) - .unwrap_or(&mut document.document_network) - } - - /// Get the active graph_craft NodeNetwork struct - fn get_active_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> { - self.get_root_network(document).nested_network(&self.nested_path) - } - - /// Get the active graph_craft NodeNetwork struct - fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> { - self.get_root_network_mut(document).nested_network_mut(&self.nested_path) - } - /// Send the cached layout to the frontend for the options bar at the top of the node panel fn send_node_bar_layout(&self, responses: &mut VecDeque) { responses.add(LayoutMessage::SendLayout { @@ -176,8 +156,8 @@ impl NodeGraphMessageHandler { None => ("File", "Document"), }; - let mut network = Some(self.get_root_network(document)); - for node_id in &self.nested_path { + let mut network = Some(&document.document_network); + for node_id in &self.network { let node = network.and_then(|network| network.nodes.get(node_id)); if let Some(DocumentNode { name, .. }) = node { @@ -210,16 +190,16 @@ impl NodeGraphMessageHandler { /// Updates the buttons for disable and preview fn update_selection_action_buttons(&mut self, document: &Document, responses: &mut VecDeque) { - if let Some(network) = self.get_active_network(document) { + if let Some(network) = document.document_network.nested_network(&self.network) { let mut widgets = Vec::new(); // Don't allow disabling input or output nodes - let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id)); + let mut selected_nodes = document.metadata.selected_nodes().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id)); // If there is at least one other selected node then show the hide or show button if selected_nodes.next().is_some() { // Check if any of the selected nodes are disabled - let is_hidden = self.selected_nodes.iter().any(|id| network.disabled.contains(id)); + let is_hidden = document.metadata.selected_nodes().any(|id| network.disabled.contains(id)); // Check if multiple nodes are selected let multiple_nodes = selected_nodes.next().is_some(); @@ -234,8 +214,8 @@ impl NodeGraphMessageHandler { } // If only one node is selected then show the preview or stop previewing button - if self.selected_nodes.len() == 1 { - let node_id = self.selected_nodes[0]; + let mut selected_nodes = document.metadata.selected_nodes(); + if let (Some(&node_id), None) = (selected_nodes.next(), selected_nodes.next()) { // Is this node the current output let is_output = network.outputs_contain(node_id); @@ -257,18 +237,20 @@ impl NodeGraphMessageHandler { /// Collate the properties panel sections for a node graph pub fn collate_properties(&self, context: &mut NodePropertiesContext, sections: &mut Vec) { let mut network = context.network; - for segment in &self.nested_path { + let document = context.document; + + for segment in &self.network { network = network.nodes.get(segment).and_then(|node| node.implementation.get_network()).unwrap(); } // If empty, show all nodes in the network starting with the output - if self.selected_nodes.is_empty() { + if !document.metadata.has_selected_nodes() { for (document_node, node_id) in network.primary_flow().collect::>().into_iter().rev() { sections.push(node_properties::generate_node_properties(document_node, node_id, context)); } } // Show properties for all selected nodes - for node_id in &self.selected_nodes { + for node_id in document.metadata.selected_nodes() { let Some(document_node) = network.nodes.get(node_id) else { continue; }; @@ -359,7 +341,7 @@ impl NodeGraphMessageHandler { fn update_selected(&mut self, document: &mut Document, responses: &mut VecDeque) { self.update_selection_action_buttons(document, responses); responses.add(FrontendMessage::UpdateNodeGraphSelection { - selected: self.selected_nodes.clone(), + selected: document.metadata.selected_nodes_ref().clone(), }); } @@ -428,14 +410,16 @@ impl NodeGraphMessageHandler { } /// Tries to remove a node from the network, returning true on success. - fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId, reconnect: bool) -> bool { - if Self::remove_references_from_network(network, node_id, reconnect) { - network.nodes.remove(&node_id); - self.selected_nodes.retain(|&id| id != node_id); - true - } else { - false + fn remove_node(&mut self, document: &mut Document, node_id: NodeId, responses: &mut VecDeque, reconnect: bool) -> bool { + let Some(network) = document.document_network.nested_network_mut(&self.network) else { + return false; + }; + if !Self::remove_references_from_network(network, node_id, reconnect) { + return false; } + network.nodes.remove(&node_id); + responses.add(document.metadata.retain_selected_nodes(|&id| id != node_id)); + true } /// Gets the default node input based on the node name and the input index @@ -464,10 +448,26 @@ pub struct NodeGraphHandlerData<'a> { } impl<'a> MessageHandler> for NodeGraphMessageHandler { - #[remain::check] fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) { - #[remain::sorted] + let document = data.document; + let document_id = data.document_id; match message { + // TODO: automatically remove broadcast messages. + NodeGraphMessage::Init => { + responses.add(BroadcastMessage::SubscribeEvent { + on: BroadcastEvent::SelectionChanged, + send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()), + }); + document.metadata.load_structure(&document.document_network); + responses.add(DocumentMessage::DocumentStructureChanged); + } + NodeGraphMessage::AddSelectNodes { nodes } => { + responses.add(document.metadata.add_selected_nodes(nodes)); + } + NodeGraphMessage::SelectedNodesUpdated => { + self.update_selection_action_buttons(document, responses); + self.update_selected(document, responses); + } NodeGraphMessage::CloseNodeGraph => {} NodeGraphMessage::ConnectNodesByLink { output_node, @@ -477,7 +477,7 @@ impl<'a> MessageHandler> for NodeGrap } => { let node_id = input_node; - let Some(network) = self.get_active_network(data.document) else { + let Some(network) = document.document_network.nested_network(&self.network) else { error!("No network"); return; }; @@ -489,6 +489,8 @@ impl<'a> MessageHandler> for NodeGrap error!("Failed to find actual index of connector index {input_node_connector_index} on node {input_node:#?}"); return; }; + document.metadata.load_structure(&document.document_network); + responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::StartTransaction); @@ -499,13 +501,13 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SendGraph { should_rerender }); } NodeGraphMessage::Copy => { - let Some(network) = self.get_active_network(data.document) else { + let Some(network) = document.document_network.nested_network(&self.network) else { error!("No network"); return; }; // Collect the selected nodes - let new_ids = &self.selected_nodes.iter().copied().enumerate().map(|(new, old)| (old, new as NodeId)).collect(); + let new_ids = &document.metadata.selected_nodes().copied().enumerate().map(|(new, old)| (old, new as NodeId)).collect(); let copied_nodes: Vec<_> = Self::copy_nodes(network, new_ids).collect(); // Prefix to show that this is nodes @@ -540,23 +542,20 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true }); } NodeGraphMessage::DeleteNode { node_id, reconnect } => { - if let Some(network) = self.get_active_network_mut(data.document) { - self.remove_node(network, node_id, reconnect); - } - self.update_selected(data.document, responses); + self.remove_node(document, node_id, responses, reconnect); } NodeGraphMessage::DeleteSelectedNodes { reconnect } => { responses.add(DocumentMessage::StartTransaction); - for node_id in self.selected_nodes.clone() { + for node_id in document.metadata.selected_nodes().copied() { responses.add(NodeGraphMessage::DeleteNode { node_id, reconnect }); } responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { // 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 document.metadata.selected_nodes().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 { @@ -566,7 +565,7 @@ impl<'a> MessageHandler> for NodeGrap } } NodeGraphMessage::DisconnectNodes { node_id, input_index } => { - let Some(network) = self.get_active_network(data.document) else { + let Some(network) = document.document_network.nested_network(&self.network) else { warn!("No network"); return; }; @@ -594,55 +593,56 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SendGraph { should_rerender }); } NodeGraphMessage::DoubleClickNode { node } => { - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() { - self.nested_path.push(node); + self.network.push(node); } } - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { Self::send_graph(network, &self.layer_path, responses); } - self.collect_nested_addresses(data.document, data.document_name, responses); - self.update_selected(data.document, responses); + self.collect_nested_addresses(document, data.document_name, responses); + self.update_selected(document, responses); } NodeGraphMessage::DuplicateSelectedNodes => { - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { responses.add(DocumentMessage::StartTransaction); - let new_ids = &self.selected_nodes.iter().map(|&id| (id, crate::application::generate_uuid())).collect(); - self.selected_nodes.clear(); + let new_ids = &document.metadata.selected_nodes().map(|&id| (id, crate::application::generate_uuid())).collect(); + responses.add(document.metadata.clear_selected_nodes()); // Copy the selected nodes let copied_nodes = Self::copy_nodes(network, new_ids).collect::>(); + + // Select the new nodes + responses.add(document.metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id))); + for (node_id, mut document_node) in copied_nodes { // Shift duplicated node document_node.metadata.position += IVec2::splat(2); - // Add new node to the list - self.selected_nodes.push(node_id); - // Insert new node into graph responses.add(NodeGraphMessage::InsertNode { node_id, document_node }); } Self::send_graph(network, &self.layer_path, responses); - self.update_selected(data.document, responses); + self.update_selected(document, responses); responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } } NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => { - self.selected_nodes.clear(); + responses.add(document.metadata.clear_selected_nodes()); for _ in 0..depth_of_nesting { - self.nested_path.pop(); + self.network.pop(); } - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { Self::send_graph(network, &self.layer_path, responses); } - self.collect_nested_addresses(data.document, data.document_name, responses); - self.update_selected(data.document, responses); + self.collect_nested_addresses(document, data.document_name, responses); + self.update_selected(document, responses); } NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => { - let Some(network) = self.get_active_network(data.document) else { + let Some(network) = document.document_network.nested_network(&self.network) else { warn!("No network"); return; }; @@ -672,17 +672,17 @@ impl<'a> MessageHandler> for NodeGrap responses.add(PropertiesPanelMessage::ResendActiveProperties); } NodeGraphMessage::InsertNode { node_id, document_node } => { - if let Some(network) = self.get_active_network_mut(data.document) { + if let Some(network) = document.document_network.nested_network_mut(&self.network) { network.nodes.insert(node_id, document_node); } } NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => { - let Some(network) = self.get_active_network_mut(data.document) else { + let Some(network) = document.document_network.nested_network_mut(&self.network) else { warn!("No network"); return; }; - for node_id in &self.selected_nodes { + for node_id in document.metadata.selected_nodes() { if let Some(node) = network.nodes.get_mut(node_id) { node.metadata.position += IVec2::new(displacement_x, displacement_y) } @@ -692,19 +692,19 @@ impl<'a> MessageHandler> for NodeGrap NodeGraphMessage::OpenNodeGraph { layer_path } => { self.layer_path = Some(layer_path); - if let Some(network) = self.get_active_network(data.document) { - self.selected_nodes.clear(); + if let Some(network) = document.document_network.nested_network(&self.network) { + responses.add(document.metadata.clear_selected_nodes()); Self::send_graph(network, &self.layer_path, responses); let node_types = document_node_types::collect_node_types(); responses.add(FrontendMessage::UpdateNodeTypes { node_types }); } - self.collect_nested_addresses(data.document, data.document_name, responses); - self.update_selected(data.document, responses); + self.collect_nested_addresses(document, data.document_name, responses); + self.update_selected(document, responses); } NodeGraphMessage::PasteNodes { serialized_nodes } => { - let Some(network) = self.get_active_network(data.document) else { + let Some(network) = document.document_network.nested_network(&self.network) else { warn!("No network"); return; }; @@ -746,22 +746,16 @@ impl<'a> MessageHandler> for NodeGrap } let nodes = new_ids.values().copied().collect(); - responses.add(NodeGraphMessage::SelectNodes { nodes }); + responses.add(NodeGraphMessage::SetSelectNodes { nodes }); responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { - document_id: data.document_id, + document_id: document_id, layer_path: Vec::new(), }), - NodeGraphMessage::SelectNodes { nodes } => { - self.selected_nodes = nodes; - self.update_selection_action_buttons(data.document, responses); - self.update_selected(data.document, responses); - responses.add(PropertiesPanelMessage::ResendActiveProperties); - } NodeGraphMessage::SendGraph { should_rerender } => { - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { Self::send_graph(network, &self.layer_path, responses); if should_rerender { if let Some(layer_path) = self.layer_path.clone() { @@ -772,9 +766,8 @@ impl<'a> MessageHandler> for NodeGrap } } } - NodeGraphMessage::SetInputValue { node_id, input_index, value } => { - if let Some(network) = self.get_active_network(data.document) { + if let Some(network) = document.document_network.nested_network(&self.network) { if let Some(node) = network.nodes.get(&node_id) { responses.add(DocumentMessage::StartTransaction); @@ -792,7 +785,7 @@ impl<'a> MessageHandler> for NodeGrap } } NodeGraphMessage::SetNodeInput { node_id, input_index, input } => { - if let Some(network) = self.get_active_network_mut(data.document) { + if let Some(network) = document.document_network.nested_network_mut(&self.network) { if let Some(node) = network.nodes.get_mut(&node_id) { node.inputs[input_index] = input } @@ -809,7 +802,7 @@ impl<'a> MessageHandler> for NodeGrap return; }; - let network = self.get_root_network_mut(data.document).nested_network_mut(node_path); + let network = document.document_network.nested_network_mut(node_path); if let Some(network) = network { if let Some(node) = network.nodes.get_mut(node_id) { @@ -824,8 +817,12 @@ impl<'a> MessageHandler> for NodeGrap } } } + NodeGraphMessage::SetSelectNodes { nodes } => { + responses.add(document.metadata.set_selected_nodes(nodes)); + responses.add(PropertiesPanelMessage::ResendActiveProperties); + } NodeGraphMessage::ShiftNode { node_id } => { - let Some(network) = self.get_active_network_mut(data.document) else { + let Some(network) = document.document_network.nested_network_mut(&self.network) else { warn!("No network"); return; }; @@ -877,22 +874,22 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::ToggleHiddenImpl); } NodeGraphMessage::ToggleHiddenImpl => { - if let Some(network) = self.get_active_network_mut(data.document) { + if let Some(network) = document.document_network.nested_network_mut(&self.network) { // Check if any of the selected nodes are hidden - if self.selected_nodes.iter().any(|id| network.disabled.contains(id)) { + if document.metadata.selected_nodes().any(|id| network.disabled.contains(id)) { // Remove all selected nodes from the disabled list - network.disabled.retain(|id| !self.selected_nodes.contains(id)); + network.disabled.retain(|id| !document.metadata.selected_nodes_ref().contains(id)); } else { let original_outputs = network.original_outputs().iter().map(|output| output.node_id).collect::>(); // Add all selected nodes to the disabled list (excluding input or output nodes) network .disabled - .extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id))); + .extend(document.metadata.selected_nodes().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id))); } 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 document.metadata.selected_nodes().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 { @@ -900,14 +897,14 @@ impl<'a> MessageHandler> for NodeGrap } } } - self.update_selection_action_buttons(data.document, responses); + self.update_selection_action_buttons(document, responses); } NodeGraphMessage::TogglePreview { node_id } => { responses.add(DocumentMessage::StartTransaction); responses.add(NodeGraphMessage::TogglePreviewImpl { node_id }); } NodeGraphMessage::TogglePreviewImpl { node_id } => { - if let Some(network) = self.get_active_network_mut(data.document) { + if let Some(network) = document.document_network.nested_network_mut(&self.network) { // Check if the node is not already being previewed if !network.outputs_contain(node_id) { network.previous_outputs = Some(network.previous_outputs.to_owned().unwrap_or_else(|| network.outputs.clone())); @@ -919,7 +916,7 @@ impl<'a> MessageHandler> for NodeGrap } Self::send_graph(network, &self.layer_path, responses); } - self.update_selection_action_buttons(data.document, responses); + self.update_selection_action_buttons(document, responses); if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } else { @@ -927,22 +924,23 @@ impl<'a> MessageHandler> for NodeGrap } } NodeGraphMessage::UpdateNewNodeGraph => { - if let Some(network) = self.get_active_network(data.document) { - self.selected_nodes.clear(); + if let Some(network) = document.document_network.nested_network(&self.network) { + responses.add(document.metadata.clear_selected_nodes()); Self::send_graph(network, &self.layer_path, responses); let node_types = document_node_types::collect_node_types(); responses.add(FrontendMessage::UpdateNodeTypes { node_types }); } - self.collect_nested_addresses(data.document, data.document_name, responses); - self.update_selected(data.document, responses); + self.collect_nested_addresses(document, data.document_name, responses); + self.update_selected(document, responses); } } + self.has_selection = document.metadata.has_selected_nodes(); } fn actions(&self) -> ActionList { - if !self.selected_nodes.is_empty() { + if self.has_selection { actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleHidden) } else { actions!(NodeGraphMessageDiscriminant;) 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 86fc35f5df..660d8c8972 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 @@ -2127,7 +2127,38 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Transform", category: "Transform", - identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _, _>"), + identifier: NodeImplementation::DocumentNode(NodeNetwork { + inputs: vec![0, 1, 1, 1, 1, 1], + outputs: vec![NodeOutput::new(1, 0)], + nodes: [ + DocumentNode { + name: "Monitor".to_string(), + inputs: vec![NodeInput::Network(concrete!(VectorData))], + implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"), + skip_deduplication: true, + ..Default::default() + }, + DocumentNode { + name: "Transform".to_string(), + inputs: vec![ + NodeInput::node(0, 0), + NodeInput::Network(concrete!(DVec2)), + NodeInput::Network(concrete!(f32)), + NodeInput::Network(concrete!(DVec2)), + NodeInput::Network(concrete!(DVec2)), + NodeInput::Network(concrete!(DVec2)), + ], + manual_composition: Some(concrete!(Footprint)), + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>")), + ..Default::default() + }, + ] + .into_iter() + .enumerate() + .map(|(id, node)| (id as NodeId, node)) + .collect(), + ..Default::default() + }), manual_composition: Some(concrete!(Footprint)), inputs: vec![ DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index a7a41f7643..717557deaf 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -1171,7 +1171,7 @@ pub fn logic_operator_properties(document_node: &DocumentNode, node_id: NodeId, vec![LayoutGroup::Row { widgets }] } -pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { let translation_assist = |widgets: &mut Vec| { let pivot_index = 5; if let NodeInput::Value { @@ -1182,7 +1182,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push( PivotAssist::new(pivot.into()) - .on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into()) + .on_update(update_value(|pivot: &PivotAssist| TaggedValue::DVec2(Into::>::into(pivot.position).unwrap()), node_id, 5)) .widget_holder(), ); } else { diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index 578904f42d..72c21268cd 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -138,7 +138,7 @@ impl<'a> MessageHandler, LayerMetadata>, } -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash)] pub enum FlipAxis { X, Y, } -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, specta::Type)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)] pub enum AlignAxis { X, Y, } -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, specta::Type)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)] pub enum AlignAggregate { Min, Max, Center, - Average, } #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index b261928df0..4ebace23a0 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -1,11 +1,13 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL}; use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::ToolType; use document_legacy::document::Document; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::style::RenderData; -use document_legacy::LayerId; +use graphene_core::renderer::Quad; use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::{DAffine2, DVec2}; @@ -13,8 +15,8 @@ use std::collections::{HashMap, VecDeque}; #[derive(Debug, PartialEq, Clone)] pub enum OriginalTransforms { - Layer(HashMap, DAffine2>), - Path(HashMap, Vec<(ManipulatorPointId, DVec2)>>), + Layer(HashMap), + Path(HashMap>), } impl Default for OriginalTransforms { fn default() -> Self { @@ -28,6 +30,51 @@ impl OriginalTransforms { OriginalTransforms::Path(path_map) => path_map.clear(), } } + + pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], responses: &'a mut VecDeque, document: &'a Document, shape_editor: Option<&'a ShapeState>, tool_type: &'a ToolType) { + match self { + OriginalTransforms::Layer(layer_map) => { + for &layer in selected { + if !layer_map.contains_key(&layer) { + layer_map.insert(layer, document.metadata.transform_to_document(layer)); + } + } + } + OriginalTransforms::Path(path_map) => { + for &layer in selected { + let Some(shape_editor) = shape_editor else { + warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType"); + continue; + }; + // Anchors also move their handles + let expand_anchors = |&point: &ManipulatorPointId| { + if point.manipulator_type.is_handle() { + [Some(point), None, None] + } else { + [ + Some(point), + Some(ManipulatorPointId::new(point.group, SelectedType::InHandle)), + Some(ManipulatorPointId::new(point.group, SelectedType::OutHandle)), + ] + } + }; + let points = shape_editor.selected_points().flat_map(expand_anchors).flatten(); + if path_map.contains_key(&layer) { + continue; + } + let Some(vector_data) = graph_modification_utils::get_subpaths(layer, document) else { + continue; + }; + let get_manipulator_point_position = |point_id: ManipulatorPointId| { + graph_modification_utils::get_manipulator_from_id(vector_data, point_id.group) + .and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group)) + .map(|position| (point_id, position)) + }; + path_map.insert(layer, points.filter_map(get_manipulator_point_position).collect()); + } + } + } + } } #[derive(Default, Debug, Clone, PartialEq, Eq, Copy)] @@ -264,7 +311,7 @@ impl TransformOperation { } pub struct Selected<'a> { - pub selected: &'a [&'a Vec], + pub selected: &'a [LayerNodeIdentifier], pub responses: &'a mut VecDeque, pub document: &'a Document, pub original_transforms: &'a mut OriginalTransforms, @@ -277,7 +324,7 @@ impl<'a> Selected<'a> { pub fn new( original_transforms: &'a mut OriginalTransforms, pivot: &'a mut DVec2, - selected: &'a [&'a Vec], + selected: &'a [LayerNodeIdentifier], responses: &'a mut VecDeque, document: &'a Document, shape_editor: Option<&'a ShapeState>, @@ -288,57 +335,8 @@ impl<'a> Selected<'a> { *original_transforms = OriginalTransforms::Layer(HashMap::new()); } - match original_transforms { - OriginalTransforms::Layer(layer_map) => { - for layer_path in selected { - if !layer_map.contains_key(*layer_path) { - if let Ok(layer) = document.layer(layer_path) { - layer_map.insert(layer_path.to_vec(), layer.transform); - } else { - warn!("Didn't find a layer for {:?}", layer_path); - } - } - } - } - OriginalTransforms::Path(path_map) => { - for path in selected { - let Some(shape_editor) = shape_editor else { - warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType"); - continue; - }; - // Anchors also move their handles - let expand_anchors = |&point: &ManipulatorPointId| { - if point.manipulator_type.is_handle() { - [Some(point), None, None] - } else { - [ - Some(point), - Some(ManipulatorPointId::new(point.group, SelectedType::InHandle)), - Some(ManipulatorPointId::new(point.group, SelectedType::OutHandle)), - ] - } - }; - let points = shape_editor.selected_points().flat_map(expand_anchors).flatten(); - if path_map.contains_key(*path) { - continue; - } - let Ok(layer) = document.layer(path) else { - warn!("Didn't find a layer for {:?}", path); - continue; - }; - let Some(vector_data) = layer.as_vector_data() else { - continue; - }; - let get_manipulator_point_position = |point_id: ManipulatorPointId| { - vector_data - .manipulator_from_id(point_id.group) - .and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group)) - .map(|position| (point_id, position)) - }; - path_map.insert(path.to_vec(), points.filter_map(get_manipulator_point_position).collect()); - } - } - } + original_transforms.update(selected, responses, document, shape_editor, tool_type); + Self { selected, responses, @@ -351,7 +349,12 @@ impl<'a> Selected<'a> { } pub fn mean_average_of_pivots(&mut self, render_data: &RenderData) -> DVec2 { - let xy_summation = self.selected.iter().filter_map(|path| self.document.pivot(path, render_data)).reduce(|a, b| a + b).unwrap_or_default(); + let xy_summation = self + .selected + .iter() + .filter_map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document)) + .reduce(|a, b| a + b) + .unwrap_or_default(); xy_summation / self.selected.len() as f64 } @@ -360,12 +363,8 @@ impl<'a> Selected<'a> { let [min, max] = self .selected .iter() - .filter_map(|path| { - let multiplied_transform = self.document.multiply_transforms(path).unwrap(); - - self.document.layer(path).unwrap().aabb_for_transform(multiplied_transform, render_data) - }) - .reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]) + .filter_map(|&layer| self.document.metadata.bounding_box_viewport(layer)) + .reduce(Quad::combine_bounds) .unwrap_or_default(); (min + max) / 2. } @@ -376,27 +375,30 @@ impl<'a> Selected<'a> { let transformation = pivot * delta * pivot.inverse(); // 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 layer_path in Document::shallowest_unique_layers(self.selected.iter()) { - let parent_folder_path = &layer_path[..layer_path.len() - 1]; + for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter()) { + let layer = *layer_ancestors.last().unwrap(); + let parent = layer.parent(&self.document.metadata); if *self.tool_type == ToolType::Select { let original_layer_transforms = match self.original_transforms { - OriginalTransforms::Layer(layer_map) => *layer_map.get(*layer_path).unwrap(), + OriginalTransforms::Layer(layer_map) => *layer_map.get(&layer).unwrap(), OriginalTransforms::Path(_path_map) => { - warn!("Found Path variant in original_transforms, returning identity transform for layer {:?}", layer_path); + warn!("Found Path variant in original_transforms, returning identity transform for layer {:?}", layer); DAffine2::IDENTITY } }; - let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); + let to = parent + .map(|parent| self.document.metadata.transform_to_viewport(parent)) + .unwrap_or(self.document.metadata.document_to_viewport); let new = to.inverse() * transformation * to * original_layer_transforms; self.responses.add(GraphOperationMessage::TransformSet { - layer: layer_path.to_vec(), + layer: layer.to_path(), transform: new, transform_in: TransformIn::Local, - skip_rerender: true, + skip_rerender: false, }); } if *self.tool_type == ToolType::Path { - let viewspace = self.document.generate_transform_relative_to_viewport(layer_path).ok().unwrap_or_default(); + let viewspace = self.document.metadata.transform_to_viewport(layer); let layerspace_rotation = viewspace.inverse() * transformation; let initial_points = match self.original_transforms { @@ -404,7 +406,7 @@ impl<'a> Selected<'a> { warn!("Found Layer variant in original_transforms when Path wanted, returning identity transform for layer"); None } - OriginalTransforms::Path(path_map) => path_map.get(*layer_path), + OriginalTransforms::Path(path_map) => path_map.get(&layer), }; let Some(original) = initial_points else { @@ -418,7 +420,7 @@ impl<'a> Selected<'a> { let position = new_pos_viewport; self.responses.add(GraphOperationMessage::Vector { - layer: (*layer_path).to_vec(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }); } @@ -429,23 +431,23 @@ impl<'a> Selected<'a> { } pub fn revert_operation(&mut self) { - for path in self.selected.iter().copied() { + for layer in self.selected.iter().copied() { let original_transform = &self.original_transforms; match original_transform { OriginalTransforms::Layer(hash) => { - let Some(matrix) = hash.get(path) else { continue }; + let Some(matrix) = hash.get(&layer) else { continue }; self.responses.add(GraphOperationMessage::TransformSet { - layer: path.to_vec(), + layer: layer.to_path(), transform: *matrix, transform_in: TransformIn::Local, skip_rerender: false, }); } OriginalTransforms::Path(path) => { - for (layer_path, points) in path { + for (layer, points) in path { for &(point, position) in points { self.responses.add(GraphOperationMessage::Vector { - layer: (*layer_path).clone(), + layer: layer.to_path(), modification: VectorDataModification::SetManipulatorPosition { point, position }, }); } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 28ed93eb6d..4f47dcfe62 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -658,10 +658,12 @@ impl PortfolioMessageHandler { responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open }); responses.add(ToolMessage::InitTools); responses.add(PropertiesPanelMessage::Init); + responses.add(NodeGraphMessage::Init); responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(PropertiesPanelMessage::ClearSelection); responses.add(PropertiesPanelMessage::UpdateSelectedDocumentProperties); + responses.add(NodeGraphMessage::UpdateNewNodeGraph); } /// Returns an iterator over the open documents in order. 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 1d22b6fb9d..33e75b5bba 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -9,15 +9,14 @@ use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::style::{FillType, Gradient}; use graphene_core::Color; -use glam::DAffine2; +use glam::{DAffine2, DVec2}; use std::collections::VecDeque; /// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. pub fn new_vector_layer(subpaths: Vec>, layer_path: Vec, responses: &mut VecDeque) { - responses.add(GraphOperationMessage::NewVectorLayer { - id: *layer_path.last().unwrap(), - subpaths, - }); + let id = *layer_path.last().unwrap(); + responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths }); + responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![id] }) } /// Creat a new bitmap layer from an [`graphene_core::raster::ImageFrame`] @@ -62,6 +61,25 @@ pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<& } } +/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work) +pub fn get_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option { + if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, document)?.find_input("Transform", 5)? { + Some(*pivot) + } else { + None + } +} + +pub fn get_document_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option { + let [min, max] = document.metadata.nonzero_bounding_box(layer); + get_pivot(layer, document).map(|pivot| document.metadata.transform_to_document(layer).transform_point2(min + (max - min) * pivot)) +} + +pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option { + let [min, max] = document.metadata.nonzero_bounding_box(layer); + get_pivot(layer, document).map(|pivot| document.metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)) +} + /// Get the currently mirrored handles for a particular layer from the shape node 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)? { @@ -106,6 +124,16 @@ pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool { NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Artboard")) } +/// Is a specified layer a shape? +pub fn is_shape_layer(layer: LayerNodeIdentifier, document: &Document) -> bool { + NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Shape")) +} + +/// Is a specified layer text? +pub fn is_text_layer(layer: LayerNodeIdentifier, document: &Document) -> bool { + NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Text")) +} + /// Convert subpaths to an iterator of manipulator groups pub fn get_manipulator_groups(subpaths: &[Subpath]) -> impl Iterator> + DoubleEndedIterator { subpaths.iter().flat_map(|subpath| subpath.manipulator_groups()) diff --git a/editor/src/messages/tool/common_functionality/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs index 1775411da4..bb29dcec72 100644 --- a/editor/src/messages/tool/common_functionality/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -51,7 +51,7 @@ impl OverlayRenderer { } 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); + let transform = document.metadata.transform_to_viewport(layer); let Some(subpaths) = get_subpaths(layer, document) else { return; diff --git a/editor/src/messages/tool/common_functionality/path_outline.rs b/editor/src/messages/tool/common_functionality/path_outline.rs index f4355f88ae..c77f70ab7f 100644 --- a/editor/src/messages/tool/common_functionality/path_outline.rs +++ b/editor/src/messages/tool/common_functionality/path_outline.rs @@ -1,19 +1,17 @@ use crate::application::generate_uuid; -use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT, SELECTION_TOLERANCE}; +use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT}; use crate::messages::prelude::*; -use document_legacy::intersection::Quad; -use document_legacy::layers::layer_info::LayerDataType; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::style::{self, Fill, RenderData, Stroke}; use document_legacy::{LayerId, Operation}; -use graphene_std::vector::subpath::Subpath; -use glam::{DAffine2, DVec2}; +use glam::DAffine2; /// Manages the overlay used by the select tool for outlining selected shapes and when hovering over a non selected shape. #[derive(Clone, Debug, Default)] pub struct PathOutline { - hovered_layer_path: Option>, + hovered_layer_path: Option, hovered_overlay_path: Option>, selected_overlay_paths: Vec>, } @@ -21,29 +19,14 @@ pub struct PathOutline { impl PathOutline { /// Creates an outline of a layer either with a pre-existing overlay or by generating a new one fn try_create_outline( - document_layer_path: Vec, + layer: LayerNodeIdentifier, overlay_path: Option>, document: &DocumentMessageHandler, responses: &mut VecDeque, render_data: &RenderData, ) -> Option> { - // Get layer data - let document_layer = document.document_legacy.layer(&document_layer_path).ok()?; - - // Get the subpath from the shape - let subpath = match &document_layer.data { - LayerDataType::Shape(shape) => Some(shape.shape.clone()), - LayerDataType::Layer(layer) => { - if let Some(vector_data) = layer.as_vector_data() { - // Vector graph output - Some(Subpath::from_bezier_rs(&vector_data.subpaths)) - } else { - // Frame graph output - Some(Subpath::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.))) - } - } - _ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)), - }?; + let subpath = document.metadata().layer_outline(layer); + let transform = document.metadata().transform_to_viewport(layer); // Generate a new overlay layer if necessary let overlay = overlay_path.unwrap_or_else(|| { @@ -70,7 +53,7 @@ impl PathOutline { responses.add(DocumentMessage::Overlays( (Operation::SetLayerTransform { path: overlay.clone(), - transform: document.document_legacy.multiply_transforms(&document_layer_path).unwrap().to_cols_array(), + transform: transform.to_cols_array(), }) .into(), )); @@ -82,14 +65,14 @@ impl PathOutline { /// /// Creates an outline, discarding the overlay on failure. fn create_outline( - document_layer_path: Vec, + layer: LayerNodeIdentifier, overlay_path: Option>, document: &DocumentMessageHandler, responses: &mut VecDeque, render_data: &RenderData, ) -> Option> { let copied_overlay_path = overlay_path.clone(); - let result = Self::try_create_outline(document_layer_path, overlay_path, document, responses, render_data); + let result = Self::try_create_outline(layer, overlay_path, document, responses, render_data); if result.is_none() { // Discard the overlay layer if it exists if let Some(overlay_path) = copied_overlay_path { @@ -112,26 +95,25 @@ impl PathOutline { /// Performs an intersect test and generates a hovered overlay if necessary pub fn intersect_test_hovered(&mut self, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, responses: &mut VecDeque, render_data: &RenderData) { // Get the layer the user is hovering over - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); - let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data); + let intersection = document.metadata().click(input.mouse.position, &document.document_legacy.document_network); - // If the user is hovering over a layer they have not already selected, then update outline - if let Some(path) = intersection.pop() { - if !document.selected_visible_layers().any(|visible| visible == path.as_slice()) { - // Updates the overlay, generating a new one if necessary - self.hovered_overlay_path = Self::create_outline(path.clone(), self.hovered_overlay_path.take(), document, responses, render_data); - if self.hovered_overlay_path.is_none() { - self.clear_hovered(responses); - } + let Some(hovered_layer) = intersection else { + self.clear_hovered(responses); + return; + }; - self.hovered_layer_path = Some(path); - } else { - self.clear_hovered(responses); - } - } else { + if document.metadata().selected_layers_contains(hovered_layer) { + self.clear_hovered(responses); + return; + } + + // Updates the overlay, generating a new one if necessary + self.hovered_overlay_path = Self::create_outline(hovered_layer, self.hovered_overlay_path.take(), document, responses, render_data); + if self.hovered_overlay_path.is_none() { self.clear_hovered(responses); } + + self.hovered_layer_path = Some(hovered_layer); } /// Clears overlays for the selected paths and removes references @@ -143,11 +125,11 @@ impl PathOutline { } /// Updates the selected overlays, generating or removing overlays if necessary - pub fn update_selected<'a>(&mut self, selected: impl Iterator, document: &DocumentMessageHandler, responses: &mut VecDeque, render_data: &RenderData) { + pub fn update_selected<'a>(&mut self, selected: impl Iterator, document: &DocumentMessageHandler, responses: &mut VecDeque, render_data: &RenderData) { let mut old_overlay_paths = std::mem::take(&mut self.selected_overlay_paths); - for document_layer_path in selected { - if let Some(overlay_path) = Self::create_outline(document_layer_path.to_vec(), old_overlay_paths.pop(), document, responses, render_data) { + for layer_identifier in selected { + if let Some(overlay_path) = Self::create_outline(layer_identifier, old_overlay_paths.pop(), document, responses, render_data) { self.selected_overlay_paths.push(overlay_path); } } diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 14330feec9..3d29d49fd2 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -5,12 +5,15 @@ use crate::consts::{COLOR_ACCENT, PIVOT_INNER, PIVOT_OUTER, PIVOT_OUTER_OUTLINE_ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::style::{self, RenderData}; use document_legacy::{LayerId, Operation}; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; +use super::graph_modification_utils; + #[derive(Clone, Debug)] pub struct Pivot { /// Pivot between (0,0) and (1,1) @@ -39,54 +42,49 @@ impl Default for Pivot { impl Pivot { /// Calculates the transform that gets from normalized pivot to viewspace. - fn get_layer_pivot_transform(layer_path: &[LayerId], layer: &document_legacy::layers::layer_info::Layer, document: &DocumentMessageHandler, render_data: &RenderData) -> DAffine2 { - let [mut min, max] = layer.aabb_for_transform(DAffine2::IDENTITY, render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]); + fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { + let [min, max] = document.metadata().nonzero_bounding_box(layer); - // If the layer bounds are 0 in either axis then set them to one (to avoid div 0) - if (max.x - min.x) < f64::EPSILON * 1000. { - min.x = max.x - 1.; - } - if (max.y - min.y) < f64::EPSILON * 1000. { - min.y = max.y - 1.; - } let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - let layer_transform = document.document_legacy.multiply_transforms(layer_path).unwrap_or(DAffine2::IDENTITY); + let layer_transform = document.metadata().transform_to_viewport(layer); layer_transform * bounds_transform } /// Recomputes the pivot position and transform. fn recalculate_pivot(&mut self, document: &DocumentMessageHandler, render_data: &RenderData) { - let mut layers = document.selected_visible_layers(); - if let Some(first) = layers.next() { - // Add one because the first item is consumed above. - let selected_layers_count = layers.count() + 1; - - // If just one layer is selected we can use its inner transform - if selected_layers_count == 1 { - if let Ok(layer) = document.document_legacy.layer(first) { - self.normalized_pivot = layer.pivot; - self.transform_from_normalized = Self::get_layer_pivot_transform(first, layer, document, render_data); - self.pivot = Some(self.transform_from_normalized.transform_point2(layer.pivot)); - } - } else { - // If more than one layer is selected we use the AABB with the mean of the pivots - let xy_summation = document - .selected_visible_layers() - .filter_map(|path| document.document_legacy.pivot(path, render_data)) - .reduce(|a, b| a + b) - .unwrap_or_default(); - - let pivot = xy_summation / selected_layers_count as f64; - self.pivot = Some(pivot); - let [min, max] = document.selected_visible_layers_bounding_box(render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]); - self.normalized_pivot = (pivot - min) / (max - min); - - self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - } - } else { + let mut layers = document.metadata().selected_visible_layers(); + let Some(first) = layers.next() else { // If no layers are selected then we revert things back to default self.normalized_pivot = DVec2::splat(0.5); self.pivot = None; + return; + }; + + // Add one because the first item is consumed above. + let selected_layers_count = layers.count() + 1; + + // If just one layer is selected we can use its inner transform (as it accounts for rotation) + if selected_layers_count == 1 { + if let Some(normalized_pivot) = graph_modification_utils::get_pivot(first, &document.document_legacy) { + self.normalized_pivot = normalized_pivot; + self.transform_from_normalized = Self::get_layer_pivot_transform(first, document); + self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot)); + } + } else { + // If more than one layer is selected we use the AABB with the mean of the pivots + let xy_summation = document + .metadata() + .selected_visible_layers() + .filter_map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.document_legacy)) + .reduce(|a, b| a + b) + .unwrap_or_default(); + + let pivot = xy_summation / selected_layers_count as f64; + self.pivot = Some(pivot); + let [min, max] = document.metadata().selected_visible_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.normalized_pivot = (pivot - min) / (max - min); + + self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); } } @@ -158,23 +156,21 @@ impl Pivot { } /// Sets the viewport position of the pivot for all selected layers. - pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) { - for layer_path in document.selected_visible_layers() { - if let Ok(layer) = document.document_legacy.layer(layer_path) { - let transform = Self::get_layer_pivot_transform(layer_path, layer, document, render_data); - let pivot = transform.inverse().transform_point2(position); - // Only update the pivot when computed position is finite. Infinite can happen when scale is 0. - if pivot.is_finite() { - let layer = layer_path.to_owned(); - responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); - } + pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + for layer in document.metadata().selected_visible_layers() { + let transform = Self::get_layer_pivot_transform(layer, document); + let pivot = transform.inverse().transform_point2(position); + // Only update the pivot when computed position is finite. Infinite can happen when scale is 0. + if pivot.is_finite() { + let layer = layer.to_path(); + responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); } } } /// Set the pivot using the normalized transform that is set above. - pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) { - self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, render_data, responses); + pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses); } /// Answers if the pointer is currently positioned over the pivot. diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index a94c311150..a32ecf76ff 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.metadata.document_to_viewport; + let root_transform = document.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.metadata.document_to_viewport; + let root_transform = document.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 f304694c62..c76ab30300 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -95,7 +95,7 @@ impl ShapeState { selected_shape_state.select_point(manipulator_point_id); // Offset to snap the selected point to the cursor - let offset = mouse_position - document.metadata.transform_from_viewport(layer).transform_point2(point_position); + let offset = mouse_position - document.metadata.transform_to_viewport(layer).transform_point2(point_position); let points = self .selected_shape_state @@ -116,7 +116,7 @@ impl ShapeState { pub fn select_all_points(&mut self, document: &Document) { for (layer, state) in self.selected_shape_state.iter_mut() { - let Some(subpaths) = get_subpaths(*layer, document) else { return }; + 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] { @@ -367,7 +367,7 @@ impl ShapeState { let Some(subpaths) = get_subpaths(layer, document) else { continue }; let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue }; - let transform = document.metadata.transform_from_viewport(layer); + let transform = document.metadata.transform_to_viewport(layer); let delta = transform.inverse().transform_vector2(delta); for &point in state.selected_points.iter() { @@ -435,7 +435,7 @@ impl ShapeState { let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(&layer)); - let transform = document.metadata.transform_from_viewport(layer); + let transform = document.metadata.transform_to_viewport(layer); for &point in state.selected_points.iter() { let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor); @@ -649,7 +649,7 @@ impl ShapeState { let mut result = None; let subpaths = get_subpaths(layer, document)?; - let viewspace = document.metadata.transform_from_viewport(layer); + let viewspace = document.metadata.transform_to_viewport(layer); for manipulator in get_manipulator_groups(subpaths) { let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE); @@ -664,7 +664,7 @@ 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: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> { - let transform = document.metadata.transform_from_viewport(layer); + let transform = document.metadata.transform_to_viewport(layer); let layer_pos = transform.inverse().transform_point2(position); let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() }; @@ -735,7 +735,7 @@ impl ShapeState { let mut process_layer = |layer| { let subpaths = get_subpaths(layer, document)?; - let transform_to_screenspace = document.metadata.transform_from_viewport(layer); + let transform_to_screenspace = document.metadata.transform_to_viewport(layer); let mut result = None; let mut closest_distance_squared = tolerance * tolerance; @@ -803,7 +803,7 @@ impl ShapeState { let Some(subpaths) = get_subpaths(layer, document) else { continue }; - let transform = document.metadata.transform_from_viewport(layer); + let transform = document.metadata.transform_to_viewport(layer); for manipulator_group in get_manipulator_groups(subpaths) { for selected_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 6d605f47b0..0f4228edbe 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -103,14 +103,14 @@ struct ArtboardToolData { impl ArtboardToolData { fn refresh_overlays(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { - let current_artboard = self.selected_artboard.and_then(|layer| document.document_legacy.metadata.bounding_box_document(layer)); + let current_artboard = self.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)); match (current_artboard, self.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 = document.document_legacy.metadata.document_to_viewport; + bounding_box_overlays.transform = document.metadata().document_to_viewport; bounding_box_overlays.transform(responses); @@ -265,7 +265,7 @@ impl Fsm for ArtboardToolFsmState { 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 root_transform = document.metadata().document_to_viewport.inverse(); let mut start = tool_data.drag_start; let mut size = snapped_mouse_position - start; diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index fafbecf7bb..129978abe5 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -341,7 +341,7 @@ impl Fsm for BrushToolFsmState { document, global_tool_data, input, .. } = tool_action_data; - let document_position = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position); + let document_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); let layer_position = tool_data.transform.inverse().transform_point2(document_position); let ToolMessage::Brush(event) = event else { diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index de6e5041c8..737eaa93f7 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -68,7 +68,7 @@ impl Fsm for FillToolFsmState { let ToolMessage::Fill(event) = event else { return self; }; - let Some(layer_identifier) = document.document_legacy.metadata.click(input.mouse.position) else { + let Some(layer_identifier) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) else { return self; }; let layer = layer_identifier.to_path(); diff --git a/editor/src/messages/tool/tool_messages/frame_tool.rs b/editor/src/messages/tool/tool_messages/frame_tool.rs index f6abe6d122..283962638c 100644 --- a/editor/src/messages/tool/tool_messages/frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/frame_tool.rs @@ -110,7 +110,7 @@ impl Fsm for NodeGraphToolFsmState { 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); + //tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); self } diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 4c74fb10db..43dbfa5b26 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -191,7 +191,7 @@ impl Fsm for FreehandToolFsmState { document, global_tool_data, input, .. } = tool_action_data; - let transform = document.document_legacy.metadata.document_to_viewport; + let transform = document.metadata().document_to_viewport; let ToolMessage::Freehand(event) = event else { return self; diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index bc1b156358..ff409272c9 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -116,10 +116,10 @@ enum GradientToolFsmState { /// Computes the transform from gradient space to viewport space (where gradient space is 0..1) fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { - let bounds = document.document_legacy.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap(); + let bounds = document.metadata().nonzero_bounding_box(layer); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); - let multiplied = document.document_legacy.metadata.transform_from_viewport(layer); + let multiplied = document.metadata().transform_to_viewport(layer); multiplied * bound_transform } @@ -252,7 +252,7 @@ impl SelectedGradient { }; // Clear the gradient if layer deleted - if !inner_gradient.layer.exists(&document.document_legacy.metadata) { + if !inner_gradient.layer.exists(&document.metadata()) { responses.add(ToolMessage::RefreshToolOptions); *gradient = None; return; @@ -391,7 +391,7 @@ impl Fsm for GradientToolFsmState { SelectedGradient::update(&mut tool_data.selected_gradient, document, responses); } - for layer in document.document_legacy.metadata.selected_visible_layers() { + for layer in document.metadata().selected_visible_layers() { if let Some(gradient) = get_gradient(layer, &document.document_legacy) { let dragging = tool_data .selected_gradient @@ -526,7 +526,7 @@ impl Fsm for GradientToolFsmState { document.backup_nonmut(responses); GradientToolFsmState::Drawing } else { - let selected_layer = document.document_legacy.metadata.click(input.mouse.position); + let selected_layer = document.metadata().click(input.mouse.position, &document.document_legacy.document_network); // Apply the gradient to the selected layer if let Some(layer) = selected_layer { @@ -540,7 +540,7 @@ impl Fsm for GradientToolFsmState { // return self; // } - if !document.document_legacy.metadata.selected_layers_contains(layer) { + if !document.metadata().selected_layers_contains(layer) { let replacement_selected_layers = vec![layer.to_path()]; responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index 1b4c8f99d7..d082b8778e 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -118,7 +118,7 @@ impl Fsm for ImaginateToolFsmState { 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); + //tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data); self } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index b4444e1301..fdaf49bdf1 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -181,7 +181,7 @@ impl Fsm for LineToolFsmState { tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); 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); + tool_data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(viewport_start); let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X); @@ -202,7 +202,7 @@ impl Fsm for LineToolFsmState { 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; + let transform = document.metadata().document_to_viewport; responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center))); LineToolFsmState::Drawing diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index d11c1b4cda..6d5b4661ba 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -210,7 +210,7 @@ struct PathToolData { 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 in document.document_legacy.metadata.all_layers() { + for layer in document.metadata().all_layers() { shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses); } @@ -242,15 +242,11 @@ impl PathToolData { PathToolFsmState::Dragging } // We didn't find a point nearby, so consider selecting the nearest shape instead - else if let Some(layer) = document.document_legacy.metadata.click(input.mouse.position) { - // TODO: Actual selection - let layer_list = vec![layer.to_path()]; + else if let Some(layer) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) { if shift { - responses.add(DocumentMessage::AddSelectedLayers { additional_layers: layer_list }); + responses.add(NodeGraphMessage::AddSelectNodes { nodes: vec![layer.to_node()] }); } else { - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: layer_list, - }); + responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![layer.to_node()] }); } self.drag_start_pos = input.mouse.position; self.previous_mouse_position = input.mouse.position; @@ -342,7 +338,7 @@ impl Fsm for PathToolFsmState { match (self, event) { (_, PathToolMessage::SelectionChanged) => { // Set the newly targeted layers to visible - let target_layers = document.document_legacy.metadata.selected_layers().collect(); + let target_layers = document.metadata().selected_layers().collect(); shape_editor.set_selected_layers(target_layers); tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses); @@ -354,7 +350,7 @@ impl Fsm for PathToolFsmState { (_, 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 in document.document_legacy.metadata.selected_layers() { + for layer in document.metadata().selected_layers() { shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); } @@ -386,7 +382,7 @@ impl Fsm for PathToolFsmState { 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); + responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] }); } 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); @@ -401,7 +397,7 @@ impl Fsm for PathToolFsmState { 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); + responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] }); } 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); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index b7ec341726..51a39eadf7 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -247,7 +247,7 @@ impl PenToolData { let layer_path = document.get_path_for_new_layer(); // Get the position and set properties - let transform = document.document_legacy.metadata.document_to_viewport * document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default(); + let transform = document.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; @@ -566,7 +566,7 @@ impl Fsm for PenToolFsmState { transform = DAffine2::IDENTITY; } - transform = document.document_legacy.metadata.document_to_viewport * transform; + transform = document.metadata().document_to_viewport * transform; let ToolMessage::Pen(event) = event else { return self; @@ -579,19 +579,19 @@ impl Fsm for PenToolFsmState { (_, 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() { + for layer in document.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() { + for layer in document.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() { + for layer in document.metadata().selected_layers() { shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses); } self diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 84a062eb76..021331c57d 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,20 +1,20 @@ #![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::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::transformation::Selected; +use crate::messages::tool::common_functionality::graph_modification_utils::is_shape_layer; +use crate::messages::tool::common_functionality::graph_modification_utils::is_text_layer; 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 document_legacy::document::Document; -use document_legacy::intersection::Quad; -use document_legacy::layers::layer_info::{Layer, LayerDataType}; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::LayerId; use document_legacy::Operation; +use graphene_core::renderer::Quad; use std::fmt; @@ -65,10 +65,6 @@ pub enum SelectToolMessage { SelectionChanged, // Tool-specific messages - Align { - axis: AlignAxis, - aggregate: AlignAggregate, - }, DragStart { add_to_selection: Key, select_deepest: Key, @@ -78,8 +74,6 @@ pub enum SelectToolMessage { }, EditLayer, Enter, - FlipHorizontal, - FlipVertical, PointerMove { axis_align: Key, snap_angle: Key, @@ -104,8 +98,8 @@ impl ToolMetadata for SelectTool { } } -impl LayoutHolder for SelectTool { - fn layout(&self) -> Layout { +impl SelectTool { + fn deep_selection_widget(&self) -> WidgetHolder { let layer_selection_behavior_entries = [NestedSelectionBehavior::Deepest, NestedSelectionBehavior::Shallowest] .iter() .map(|mode| { @@ -115,137 +109,92 @@ impl LayoutHolder for SelectTool { }) .collect(); - let selected_layers_count = self.tool_data.selected_layers_count; - let deactivate_alignment = selected_layers_count < 2; - let deactivate_boolean_ops = selected_layers_count < 2; - let deactivate_flip = selected_layers_count < 1; - let deactivate_pivot = selected_layers_count < 1; + DropdownInput::new(vec![layer_selection_behavior_entries]) + .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest) as u32)) + .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") + .widget_holder() + } - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![ - DropdownInput::new(vec![layer_selection_behavior_entries]) - .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest) as u32)) - .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - // We'd like this widget to hide and show itself whenever the transformation cage is active or inactive (i.e. when no layers are selected) - PivotAssist::new(self.tool_data.pivot.to_pivot_position()) - .disabled(deactivate_pivot) - .on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()) - .widget_holder(), - Separator::new(SeparatorType::Section).widget_holder(), - IconButton::new("AlignLeft", 24) - .tooltip("Align Left") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::X, - aggregate: AlignAggregate::Min, - } - .into() - }) - .widget_holder(), - IconButton::new("AlignHorizontalCenter", 24) - .tooltip("Align Horizontal Center") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::X, - aggregate: AlignAggregate::Center, - } - .into() - }) - .widget_holder(), - IconButton::new("AlignRight", 24) - .tooltip("Align Right") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::X, - aggregate: AlignAggregate::Max, - } - .into() - }) - .widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - IconButton::new("AlignTop", 24) - .tooltip("Align Top") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::Y, - aggregate: AlignAggregate::Min, - } - .into() - }) - .widget_holder(), - IconButton::new("AlignVerticalCenter", 24) - .tooltip("Align Vertical Center") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::Y, - aggregate: AlignAggregate::Center, - } - .into() - }) - .widget_holder(), - IconButton::new("AlignBottom", 24) - .tooltip("Align Bottom") - .disabled(deactivate_alignment) - .on_update(|_| { - DocumentMessage::AlignSelectedLayers { - axis: AlignAxis::Y, - aggregate: AlignAggregate::Max, - } - .into() - }) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - PopoverButton::new("Align", "Coming soon").disabled(deactivate_alignment).widget_holder(), - Separator::new(SeparatorType::Section).widget_holder(), - IconButton::new("FlipHorizontal", 24) - .tooltip("Flip Horizontal") - .disabled(deactivate_flip) - .on_update(|_| SelectToolMessage::FlipHorizontal.into()) - .widget_holder(), - IconButton::new("FlipVertical", 24) - .tooltip("Flip Vertical") - .disabled(deactivate_flip) - .on_update(|_| SelectToolMessage::FlipVertical.into()) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - PopoverButton::new("Flip", "Coming soon").disabled(deactivate_flip).widget_holder(), - Separator::new(SeparatorType::Section).widget_holder(), - IconButton::new("BooleanUnion", 24) - .tooltip("Coming Soon: Boolean Union") - .disabled(deactivate_boolean_ops) - .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) - .widget_holder(), - IconButton::new("BooleanSubtractFront", 24) - .tooltip("Coming Soon: Boolean Subtract Front") - .disabled(deactivate_boolean_ops) - .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) - .widget_holder(), - IconButton::new("BooleanSubtractBack", 24) - .tooltip("Coming Soon: Boolean Subtract Back") - .disabled(deactivate_boolean_ops) - .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) - .widget_holder(), - IconButton::new("BooleanIntersect", 24) - .tooltip("Coming Soon: Boolean Intersect") - .disabled(deactivate_boolean_ops) - .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) - .widget_holder(), - IconButton::new("BooleanDifference", 24) - .tooltip("Coming Soon: Boolean Difference") - .disabled(deactivate_boolean_ops) - .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - PopoverButton::new("Boolean Operations", "Coming soon").disabled(deactivate_boolean_ops).widget_holder(), - ], - }])) + fn pivot_widget(&self, disabled: bool) -> WidgetHolder { + PivotAssist::new(self.tool_data.pivot.to_pivot_position()) + .on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()) + .disabled(disabled) + .widget_holder() + } + + fn alignment_widgets(&self, disabled: bool) -> impl Iterator { + [AlignAxis::X, AlignAxis::Y] + .into_iter() + .flat_map(|axis| [(axis, AlignAggregate::Min), (axis, AlignAggregate::Center), (axis, AlignAggregate::Max)]) + .map(move |(axis, aggregate)| { + let (icon, tooltip) = match (axis, aggregate) { + (AlignAxis::X, AlignAggregate::Min) => ("AlignLeft", "Align Left"), + (AlignAxis::X, AlignAggregate::Center) => ("AlignHorizontalCenter", "Align Horizontal Center"), + (AlignAxis::X, AlignAggregate::Max) => ("AlignRight", "Align Right"), + (AlignAxis::Y, AlignAggregate::Min) => ("AlignTop", "Align Top"), + (AlignAxis::Y, AlignAggregate::Center) => ("AlignVerticalCenter", "Align Vertical Center"), + (AlignAxis::Y, AlignAggregate::Max) => ("AlignBottom", "Align Bottom"), + }; + IconButton::new(icon, 24) + .tooltip(tooltip) + .on_update(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into()) + .disabled(disabled) + .widget_holder() + }) + } + + fn flip_widgets(&self, disabled: bool) -> impl Iterator { + [(FlipAxis::X, "Horizontal"), (FlipAxis::Y, "Vertical")].into_iter().map(move |(flip_axis, name)| { + IconButton::new("Flip".to_string() + name, 24) + .tooltip("Flip ".to_string() + name) + .on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into()) + .disabled(disabled) + .widget_holder() + }) + } + + fn boolean_widgets(&self) -> impl Iterator { + ["Union", "Subtract Front", "Subtract Back", "Intersect", "Difference"].into_iter().map(|name| { + IconButton::new(format!("Boolean{}", name.replace(" ", "")), 24) + .tooltip(format!("Boolean {name} (coming soon)")) + .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into()) + .widget_holder() + }) + } +} + +impl LayoutHolder for SelectTool { + fn layout(&self) -> Layout { + let mut widgets = Vec::new(); + // widgets.push(self.deep_selection_widget()); // TODO: Reenable once Deep/Shallow Selection is implemented again + + // Pivot + // widgets.push(Separator::new(SeparatorType::Related).widget_holder()); // TODO: Reenable once Deep/Shallow Selection is implemented again + widgets.push(self.pivot_widget(self.tool_data.selected_layers_count == 0)); + + // Align + let disabled = self.tool_data.selected_layers_count < 2; + widgets.push(Separator::new(SeparatorType::Section).widget_holder()); + widgets.extend(self.alignment_widgets(disabled)); + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + widgets.push(PopoverButton::new("Align", "Coming soon").disabled(disabled).widget_holder()); + + // Flip + let disabled = self.tool_data.selected_layers_count == 0; + widgets.push(Separator::new(SeparatorType::Section).widget_holder()); + widgets.extend(self.flip_widgets(disabled)); + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + widgets.push(PopoverButton::new("Flip", "Coming soon").disabled(disabled).widget_holder()); + + // Boolean + if self.tool_data.selected_layers_count >= 2 { + widgets.push(Separator::new(SeparatorType::Section).widget_holder()); + widgets.extend(self.boolean_widgets()); + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + widgets.push(PopoverButton::new("Boolean", "Coming soon").widget_holder()); + } + + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) } } @@ -259,7 +208,7 @@ impl<'a> MessageHandler> for SelectT self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed { - // Notify the frontend about the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) + // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) self.send_layout(responses, LayoutTarget::ToolOptions); self.tool_data.selected_layers_changed = false; } @@ -313,10 +262,11 @@ enum SelectToolFsmState { struct SelectToolData { drag_start: ViewportPosition, drag_current: ViewportPosition, - layers_dragging: Vec>, - layer_selected_on_start: Option>, - is_dragging: bool, - not_duplicated_layers: Option>>, + layers_dragging: Vec, + layer_selected_on_start: Option, + select_single_layer: Option, + has_dragged: bool, + not_duplicated_layers: Option>, drag_box_overlay_layer: Option>, path_outlines: PathOutline, bounding_box_overlays: Option, @@ -351,10 +301,11 @@ impl SelectToolData { self.not_duplicated_layers = Some(self.layers_dragging.clone()); // Duplicate each previously selected layer and select the new ones. - for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) { + for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter()) { + let layer = layer_ancestors.last().unwrap(); // Moves the original back to its starting position. responses.add_front(GraphOperationMessage::TransformChange { - layer: layer_path.clone(), + layer: layer.to_path(), transform: DAffine2::from_translation(self.drag_start - self.drag_current), transform_in: TransformIn::Viewport, skip_rerender: true, @@ -362,53 +313,55 @@ impl SelectToolData { // Copy the layers. // Not using the Copy message allows us to retrieve the ids of the new layers to initialize the drag. - let layer = match document.document_legacy.layer(layer_path) { - Ok(layer) => layer.clone(), - Err(e) => { - warn!("Could not access selected layer {:?}: {:?}", layer_path, e); - continue; - } - }; + todo!(); + // let layer = match document.document_legacy.layer(layer_path) { + // Ok(layer) => layer.clone(), + // Err(e) => { + // warn!("Could not access selected layer {:?}: {:?}", layer_path, e); + // continue; + // } + // }; - let layer_metadata = *document.layer_metadata(layer_path); - *layer_path.last_mut().unwrap() = generate_uuid(); + // let layer_metadata = *document.layer_metadata(layer_path); + // *layer_path.last_mut().unwrap() = generate_uuid(); - responses.add(Operation::InsertLayer { - layer: Box::new(layer), - destination_path: layer_path.clone(), - insert_index: -1, - duplicating: false, - }); - responses.add(DocumentMessage::UpdateLayerMetadata { - layer_path: layer_path.clone(), - layer_metadata, - }); + // responses.add(Operation::InsertLayer { + // layer: Box::new(layer), + // destination_path: layer_path.clone(), + // insert_index: -1, + // duplicating: false, + // }); + // responses.add(DocumentMessage::UpdateLayerMetadata { + // layer_path: layer_path.clone(), + // layer_metadata, + // }); } // Since the selected layers have now moved back to their original transforms before the drag began, we rerender them to be displayed as if they weren't touched. for layer_path in self.not_duplicated_layers.iter().flatten() { - responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.clone() }); + //responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.clone() }); } } /// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated. - fn stop_duplicates(&mut self, responses: &mut VecDeque) { - let originals = match self.not_duplicated_layers.take() { - Some(x) => x, - None => return, + fn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let Some(originals) = self.not_duplicated_layers.take() else { + return; }; responses.add(DocumentMessage::DeselectAllLayers); // Delete the duplicated layers - for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter()) { - responses.add(Operation::DeleteLayer { path: layer_path.clone() }); + for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter()) { + responses.add(GraphOperationMessage::DeleteLayer { + id: layer_ancestors.last().unwrap().to_node(), + }); } // Move the original to under the mouse - for layer_path in Document::shallowest_unique_layers(originals.iter()) { + for layer_ancestors in document.metadata().shallowest_unique_layers(originals.iter()) { responses.add_front(GraphOperationMessage::TransformChange { - layer: layer_path.clone(), + layer: layer_ancestors.last().unwrap().to_path(), transform: DAffine2::from_translation(self.drag_current - self.drag_start), transform_in: TransformIn::Viewport, skip_rerender: true, @@ -416,8 +369,8 @@ impl SelectToolData { } // Select the originals - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: originals.clone(), + responses.add(NodeGraphMessage::SetSelectNodes { + nodes: originals.iter().map(|layer| layer.to_node()).collect::>(), }); self.layers_dragging = originals; @@ -428,33 +381,22 @@ impl Fsm for SelectToolFsmState { type ToolData = SelectToolData; 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 SelectToolFsmState::*; - use SelectToolMessage::*; + 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 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; + (_, SelectToolMessage::DocumentIsDirty | SelectToolMessage::SelectionChanged) => { + let selected_layers_count = document.metadata().selected_layers().count(); + tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; + tool_data.selected_layers_count = 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; - } + tool_data.path_outlines.update_selected(document.metadata().selected_layers(), document, responses, render_data); + tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data); - match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) { + match (document.metadata().selected_visible_layers_bounding_box_viewport(), 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)); @@ -469,31 +411,22 @@ impl Fsm for SelectToolFsmState { (_, _) => {} }; - 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), - } + (_, SelectToolMessage::EditLayer) => { + // Edit the clicked layer + if let Some(intersect) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) { + match tool_data.nested_selection_behavior { + NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, tool_data, responses), + NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.document_legacy, responses), } } self } - (Ready, DragStart { add_to_selection, select_deepest }) => { + (SelectToolFsmState::Ready, SelectToolMessage::DragStart { add_to_selection, select_deepest }) => { tool_data.path_outlines.clear_hovered(responses); tool_data.drag_start = input.mouse.position; @@ -517,9 +450,9 @@ impl Fsm for SelectToolFsmState { .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) .unwrap_or_default(); - let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect(); + let mut selected: Vec<_> = document.metadata().selected_visible_layers().collect(); let quad = tool_data.selection_quad(); - let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data); + let intersection = document.metadata().click(input.mouse.position, &document.document_legacy.document_network); // 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. @@ -532,30 +465,29 @@ impl Fsm for SelectToolFsmState { 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, &[], &[], &[]); - DraggingPivot + SelectToolFsmState::DraggingPivot } else 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; - 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::>(), &[]); + // 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::>(), &[]); tool_data.layers_dragging = selected; if let Some(bounds) = &mut tool_data.bounding_box_overlays { let document = &document.document_legacy; - let selected = &tool_data.layers_dragging.iter().collect::>(); let mut selected = Selected::new( &mut bounds.original_transforms, &mut bounds.center_of_transformation, - selected, + &tool_data.layers_dragging, responses, document, None, @@ -564,12 +496,11 @@ impl Fsm for SelectToolFsmState { bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); } - ResizingBounds + SelectToolFsmState::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, @@ -585,34 +516,38 @@ impl Fsm for SelectToolFsmState { 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) + SelectToolFsmState::RotatingBounds + } else if intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { responses.add(DocumentMessage::StartTransaction); + tool_data.select_single_layer = intersection; 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); + // tool_data + // .snap_manager + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); - Dragging + SelectToolFsmState::Dragging } else { - if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { + responses.add(DocumentMessage::StartTransaction); + tool_data.layers_dragging = selected; + + if !input.keyboard.key(add_to_selection) && 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()); + if let Some(intersection) = intersection { + tool_data.layer_selected_on_start = Some(intersection); 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), + NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(responses, selected, tool_data, document), + NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data), } - Dragging + SelectToolFsmState::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 @@ -621,53 +556,53 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging.clear(); } tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses)); - DrawingBox + SelectToolFsmState::DrawingBox } }; tool_data.not_duplicated_layers = None; state } - (Dragging, PointerMove { axis_align, duplicate, .. }) => { - tool_data.is_dragging = true; + (SelectToolFsmState::Dragging, SelectToolMessage::PointerMove { axis_align, duplicate, .. }) => { + tool_data.has_dragged = 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_position = axis_align_drag(input.keyboard.key(axis_align), 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()?) + .filter_map(|&layer| document.metadata().bounding_box_viewport(layer)) .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()) { + for layer_ancestors in document.metadata().shallowest_unique_layers(tool_data.layers_dragging.iter()) { responses.add_front(GraphOperationMessage::TransformChange { - layer: path.to_vec(), + layer: layer_ancestors.last().unwrap().to_path(), transform: DAffine2::from_translation(mouse_delta + closest_move), transform_in: TransformIn::Viewport, - skip_rerender: true, + skip_rerender: false, }); } tool_data.drag_current = mouse_position + closest_move; - if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() { + if input.keyboard.key(duplicate) && 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); + } else if !input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_some() { + tool_data.stop_duplicates(document, responses); } - Dragging + SelectToolFsmState::Dragging } - (ResizingBounds, PointerMove { axis_align, center, .. }) => { + (SelectToolFsmState::ResizingBounds, SelectToolMessage::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 (center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align)); let mouse_position = input.mouse.position; @@ -676,15 +611,15 @@ impl Fsm for SelectToolFsmState { 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 selected = &tool_data.layers_dragging; let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select); selected.update_transforms(delta); } } - ResizingBounds + SelectToolFsmState::ResizingBounds } - (RotatingBounds, PointerMove { snap_angle, .. }) => { + (SelectToolFsmState::RotatingBounds, SelectToolMessage::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; @@ -693,7 +628,7 @@ impl Fsm for SelectToolFsmState { start_offset.angle_between(end_offset) }; - let snapped_angle = if input.keyboard.get(snap_angle as usize) { + let snapped_angle = if input.keyboard.key(snap_angle) { let snap_resolution = ROTATE_SNAP_ANGLE.to_radians(); (angle / snap_resolution).round() * snap_resolution } else { @@ -702,11 +637,10 @@ impl Fsm for SelectToolFsmState { 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, + &tool_data.layers_dragging, responses, &document.document_legacy, None, @@ -716,16 +650,16 @@ impl Fsm for SelectToolFsmState { selected.update_transforms(delta); } - RotatingBounds + SelectToolFsmState::RotatingBounds } - (DraggingPivot, PointerMove { .. }) => { + (SelectToolFsmState::DraggingPivot, SelectToolMessage::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); + tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses); - DraggingPivot + SelectToolFsmState::DraggingPivot } - (DrawingBox, PointerMove { .. }) => { + (SelectToolFsmState::DrawingBox, SelectToolMessage::PointerMove { .. }) => { tool_data.drag_current = input.mouse.position; responses.add_front(DocumentMessage::Overlays( @@ -735,9 +669,9 @@ impl Fsm for SelectToolFsmState { } .into(), )); - DrawingBox + SelectToolFsmState::DrawingBox } - (Ready, PointerMove { .. }) => { + (SelectToolFsmState::Ready, SelectToolMessage::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 @@ -757,9 +691,9 @@ impl Fsm for SelectToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - Ready + SelectToolFsmState::Ready } - (Dragging, Enter) => { + (SelectToolFsmState::Dragging, SelectToolMessage::Enter) => { rerender_selected_layers(tool_data, responses); let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { @@ -769,35 +703,44 @@ impl Fsm for SelectToolFsmState { tool_data.snap_manager.cleanup(responses); responses.add_front(response); - Ready + SelectToolFsmState::Ready } - (Dragging, DragStop { remove_from_selection }) => { + (SelectToolFsmState::Dragging, SelectToolMessage::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() { + if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && 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); + let intersection = document.metadata().intersect_quad(quad, &document.document_legacy.document_network); 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(); + let replacement_selected_layers: Vec<_> = document.metadata().selected_layers().filter(|&layer| !path.starts_with(layer, document.metadata())).collect(); tool_data.layers_dragging.clear(); - tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut()); + tool_data.layers_dragging.extend(replacement_selected_layers.iter()); - responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers }); + responses.add(NodeGraphMessage::SetSelectNodes { + nodes: replacement_selected_layers.iter().map(|layer| layer.to_node()).collect(), + }); + } + } else if let Some(selecting_layer) = tool_data.select_single_layer.take() { + if !tool_data.has_dragged { + responses.add(NodeGraphMessage::SetSelectNodes { + nodes: vec![selecting_layer.to_node()], + }); } } - tool_data.is_dragging = false; + tool_data.has_dragged = false; tool_data.layer_selected_on_start = None; responses.add(DocumentMessage::CommitTransaction); tool_data.snap_manager.cleanup(responses); + tool_data.select_single_layer = None; - Ready + SelectToolFsmState::Ready } - (ResizingBounds, DragStop { .. } | Enter) => { + (SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => { rerender_selected_layers(tool_data, responses); let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { @@ -812,9 +755,9 @@ impl Fsm for SelectToolFsmState { bounds.original_transforms.clear(); } - Ready + SelectToolFsmState::Ready } - (RotatingBounds, DragStop { .. } | Enter) => { + (SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => { rerender_selected_layers(tool_data, responses); let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { @@ -827,9 +770,9 @@ impl Fsm for SelectToolFsmState { bounds.original_transforms.clear(); } - Ready + SelectToolFsmState::Ready } - (DraggingPivot, DragStop { .. } | Enter) => { + (SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => { let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { true => DocumentMessage::Undo, false => DocumentMessage::CommitTransaction, @@ -838,14 +781,14 @@ impl Fsm for SelectToolFsmState { tool_data.snap_manager.cleanup(responses); - Ready + SelectToolFsmState::Ready } - (DrawingBox, DragStop { .. } | Enter) => { + (SelectToolFsmState::DrawingBox, SelectToolMessage::DragStop { .. } | SelectToolMessage::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), + tool_data.layers_dragging = document.metadata().intersect_quad(quad, &document.document_legacy.document_network).collect(); + responses.add_front(NodeGraphMessage::SetSelectNodes { + nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); responses.add_front(DocumentMessage::Overlays( Operation::DeleteLayer { @@ -853,9 +796,9 @@ impl Fsm for SelectToolFsmState { } .into(), )); - Ready + SelectToolFsmState::Ready } - (Ready, Enter) => { + (SelectToolFsmState::Ready, SelectToolMessage::Enter) => { let mut selected_layers = document.selected_layers(); if let Some(layer_path) = selected_layers.next() { @@ -872,9 +815,9 @@ impl Fsm for SelectToolFsmState { } } - Ready + SelectToolFsmState::Ready } - (Dragging, Abort) => { + (SelectToolFsmState::Dragging, SelectToolMessage::Abort) => { rerender_selected_layers(tool_data, responses); tool_data.snap_manager.cleanup(responses); @@ -883,18 +826,17 @@ impl Fsm for SelectToolFsmState { tool_data.path_outlines.clear_selected(responses); tool_data.pivot.clear_overlays(responses); - Ready + SelectToolFsmState::Ready } - (_, Abort) => { + (_, SelectToolMessage::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, + &tool_data.layers_dragging, responses, &document.document_legacy, None, @@ -911,28 +853,13 @@ impl Fsm for SelectToolFsmState { tool_data.pivot.clear_overlays(responses); tool_data.snap_manager.cleanup(responses); - Ready + SelectToolFsmState::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 }) => { + (_, SelectToolMessage::SetPivot { position }) => { responses.add(DocumentMessage::StartTransaction); let pos: Option = position.into(); - tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses); + tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses); self } @@ -990,360 +917,59 @@ impl Fsm for SelectToolFsmState { } fn rerender_selected_layers(tool_data: &mut SelectToolData, responses: &mut VecDeque) { - for layer_path in &tool_data.layers_dragging { - responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.clone() }); - } + // for layer in &tool_data.layers_dragging { + // responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer.to_path() }); + // } +} +fn drag_shallowest_manipulation(responses: &mut VecDeque, mut selected: Vec, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) { + let layer = selected[0]; + let ancestor = layer.ancestors(document.metadata()).find(|&ancestor| document.metadata().selected_layers_contains(ancestor)); + + let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata())); + + tool_data.layers_dragging = vec![new_selected]; + responses.add(NodeGraphMessage::SetSelectNodes { + nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), + }); + // tool_data + // .snap_manager + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); } -// TODO: Majorly clean up these next five functions - -fn drag_shallowest_manipulation( - document: &DocumentMessageHandler, - selected: Vec>, - input: &InputPreprocessorMessageHandler, - select_deepest: Key, - add_to_selection: Key, - tool_data: &mut SelectToolData, - responses: &mut VecDeque, -) { - let selected_layers = document.selected_layers(); - let selected_layers_collected: Vec<_> = document.selected_layers().collect(); - let selected_layers_count = document.selected_layers().count(); - - let empty_vector: &Vec = &vec![]; - let incoming_layer_path_vector: &Vec = selected.get(0).unwrap_or(empty_vector); - - let incoming_parent = *incoming_layer_path_vector.first().unwrap_or(&0); - let previous_parents: Vec<_> = (0..selected_layers_collected.len()) - .map(|i| &selected_layers_collected.get(i).unwrap_or(&empty_vector.as_slice())[..1]) - .collect(); - - // Permutations of intersected layer - let intersected_layer_ancestors: Vec> = (1..=incoming_layer_path_vector.len()).map(|i| incoming_layer_path_vector[..i].to_vec()).rev().collect(); - - // Accel+Shift click adds the deepest layer to the selection - if input.keyboard.get(select_deepest as usize) && input.keyboard.get(add_to_selection as usize) { - // Collect the layers unrelated to the incoming layer path's parent - let mut layers_without_incoming_parent: Vec> = document - .selected_layers() - .filter(|&layer| layer.first().unwrap_or(&0) != &incoming_parent) - .map(|path| path.to_vec()) - .collect(); - // If the incoming layer is unrelated, add shape to selected layers - if !previous_parents.contains(&[incoming_parent].as_slice()) { - layers_without_incoming_parent.push(incoming_layer_path_vector.to_vec()); - tool_data.layers_dragging = layers_without_incoming_parent.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: layers_without_incoming_parent.to_vec(), - }); - } - // If the incoming shape is related, calculate the new layer tree - else { - // Calculating the next selected layers depends on how many layers are currently selected - if selected_layers_count <= 1 { - let replace = &selected_layers - .into_iter() - .any(|selected_layer| intersected_layer_ancestors.iter().any(|ancestor| selected_layer == ancestor)); - if *replace { - // If the incoming layer path is already selected, deselect - if vec![incoming_layer_path_vector] == selected_layers_collected { - tool_data.layer_selected_on_start = None; - } else { - layers_without_incoming_parent.push(incoming_layer_path_vector.to_vec()); - tool_data.layers_dragging = layers_without_incoming_parent.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: layers_without_incoming_parent.to_vec(), - }); - } - } else { - layers_without_incoming_parent.push(incoming_layer_path_vector.to_vec()); - tool_data.layers_dragging.append(layers_without_incoming_parent.to_vec().as_mut()); - responses.add(DocumentMessage::AddSelectedLayers { - additional_layers: layers_without_incoming_parent.to_vec(), - }); - } - } - // Multiple layers are selected - else { - // If the incoming layer path is already selected, deselect - if selected_layers_collected.contains(&incoming_layer_path_vector.as_slice()) { - tool_data.layer_selected_on_start = None; - } else { - layers_without_incoming_parent = document - .selected_layers() - .filter(|&layer| !intersected_layer_ancestors.contains(&(*layer).to_vec())) - .map(|path| path.to_vec()) - .collect(); - layers_without_incoming_parent.push(incoming_layer_path_vector.to_vec()); - tool_data.layers_dragging = layers_without_incoming_parent.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: layers_without_incoming_parent.to_vec(), - }); - } - } - } - - tool_data.layers_dragging.append(selected.clone().as_mut()); - responses.add(DocumentMessage::AddSelectedLayers { additional_layers: selected.clone() }); - } - // Accel click selects the deepest layer directly - else if input.keyboard.get(select_deepest as usize) { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging = selected.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: selected.to_vec(), - }); - } - // Check whether a layer is selected for next selection calculations - else if !selected_layers_collected.is_empty() { - // Check if the intersected layer path is already selected - let already_selected_parent = previous_parents.contains(&[incoming_parent].as_slice()); - let mut search = vec![]; - let mut recursive_found = false; - - // Only need to calculate if the incoming layer shares a parent with the selected layer - if already_selected_parent { - // Use the combined layers of currently selected and incoming and get shallowest common folder - let mut combined_layers = selected_layers_collected.to_vec(); - if !combined_layers.contains(&incoming_layer_path_vector.as_slice()) && !incoming_layer_path_vector.is_empty() { - 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.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 - let already_selected = selected_layers - .filter(|layer| incoming_layer_path_vector.len() >= layer.len()) - .any(|layer| &incoming_layer_path_vector[..layer.len()] == layer); - - // Recursively search through each children of the selected layer path parent, if not found move up a layer - while !selected_layer_path_parent.is_empty() && !recursive_found { - let selected_children_layer_paths = document.document_legacy.folder_children_paths(&selected_layer_path_parent); - for child in selected_children_layer_paths { - if child == *incoming_layer_path_vector { - search = child; - recursive_found = true; - break; - } else if document.document_legacy.is_folder(&child) { - recursive_found = recursive_search(document, &child, incoming_layer_path_vector); - if recursive_found { - search = child; - break; - } - } - } - selected_layer_path_parent = selected_layer_path_parent[..selected_layer_path_parent.len() - 1].to_vec(); - } - - // Check if new layer is already selected - let already_selected_search = selected_layers_collected.contains(&search.to_vec().as_slice()); - - // One layer is currently selected - if selected_layers_count <= 1 { - // If the shape has no parent and is not a folder use the incoming layer path - if search.is_empty() { - search = incoming_layer_path_vector.to_vec(); - } - - if input.keyboard.get(add_to_selection as usize) { - if !already_selected { - tool_data.layers_dragging.push(search.to_vec()); - responses.add(DocumentMessage::AddSelectedLayers { additional_layers: vec![search] }); - } - // Deselect already selected layers - else { - tool_data.layer_selected_on_start = None; - } - } else { - // Only reselect the shape if the shape is not already selected, otherwise do nothing - if !already_selected { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging.push(search.to_vec()); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![search], - }); - } - } - } else if selected_layers_count > 1 { - let direct_child = incoming_layer_path_vector - .into_iter() - .filter(|path| !shallowest_common_folder.contains(path)) - .take(1) - .copied() - .collect::>(); - 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(); - let mut replacement_selected_layers_before_filter = selected_layers_collected.to_vec(); - - // Only use the recursive search layer if a common folder between incoming layer and already selected layers exists - // If not use the rootmost parent's layer path of the incoming layer (direct_child) - if !search.is_empty() & !replacement_selected_layers_before_filter.contains(&search.as_slice()) { - replacement_selected_layers_before_filter.push(&search); - } else if search.is_empty() && !replacement_selected_layers_before_filter.contains(&direct_child.as_slice()) { - replacement_selected_layers_before_filter.push(&direct_child); - } - - // Using the shallowest common folder filter out any already selected layer that is related to the incoming selection - replacement_selected_layers_before_filter - .into_iter() - .filter(|layer| { - if !search.is_empty() { - layer.len() >= search.len() && layer[..search.len()] != search - } else { - layer.len() >= direct_child.len() && layer[..direct_child.len()] != direct_child - } - }) - .for_each(|layer| replacement_selected_layers.push(layer.to_vec())); - // After filtering any layer that is related to the incoming selection, reappend the incoming selection - if !search.is_empty() && !replacement_selected_layers.contains(&search) { - replacement_selected_layers.push(search.to_vec()); - } else if search.is_empty() && !replacement_selected_layers.contains(&direct_child) { - replacement_selected_layers.push(direct_child.to_vec()); - } - - let replacement_selected_layers_response: Vec> = replacement_selected_layers.to_vec().iter().map(|slice| slice.to_vec()).collect(); - - // Shift-Click: Append or Deselect layers - if input.keyboard.get(add_to_selection as usize) { - if search.is_empty() { - // Deselect already selected layer - if already_selected_direct_child { - tool_data.layer_selected_on_start = None; - } else { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging = replacement_selected_layers_response.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: replacement_selected_layers_response, - }); - } - } else { - // Deselect already selected layer - if already_selected_search { - tool_data.layer_selected_on_start = None; - } else { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging = replacement_selected_layers_response.to_vec(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: replacement_selected_layers_response, - }); - } - } - } - // Normal Click: Selecting new layers - else if !already_selected { - if !search.is_empty() { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging = vec![search.to_vec()]; - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![search], - }); - } else { - tool_data.layers_dragging.clear(); - tool_data.layers_dragging = vec![direct_child.to_vec()]; - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![direct_child], - }); - } - } - } - } - // Incoming layer path has different parent, set selected layer to shallowest parent - else { - if input.keyboard.get(add_to_selection as usize) { - responses.add(DocumentMessage::AddSelectedLayers { - additional_layers: vec![vec![incoming_parent]], - }); - } else { - tool_data.layers_dragging.clear(); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![vec![incoming_parent]], - }); - } - tool_data.layers_dragging.push(vec![incoming_parent]); - } - } else { - // Check if new layer is already selected - tool_data.layers_dragging.push(vec![incoming_parent]); - responses.add(DocumentMessage::AddSelectedLayers { - additional_layers: vec![vec![incoming_parent]], - }); - } +fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Vec, tool_data: &mut SelectToolData) { + tool_data.layers_dragging.append(&mut selected); + responses.add(NodeGraphMessage::SetSelectNodes { + nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), + }); + // tool_data + // .snap_manager + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); } -fn drag_deepest_manipulation( - responses: &mut VecDeque, - mut selected: Vec>, - tool_data: &mut SelectToolData, - document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - render_data: &document_legacy::layers::RenderData, -) { - responses.add(DocumentMessage::AddSelectedLayers { additional_layers: selected.clone() }); - tool_data.layers_dragging.append(selected.as_mut()); - tool_data - .snap_manager - .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); -} +fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, tool_data: &mut SelectToolData, responses: &mut VecDeque) { + if document.metadata().selected_layers_contains(layer) { + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); + return; + } -fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, intersect_layer_path: &Vec, tool_data: &mut SelectToolData, responses: &mut VecDeque) { - // Double-clicking any layer within an already selected folder should select that layer - // Add the first layer path not already included from the intersected to our new layer path let selected_layers_collected: Vec<_> = document.selected_layers().collect(); - let empty_vector: &Vec = &vec![]; - let incoming_parent = *intersect_layer_path.first().unwrap_or(&0); - let previous_parents: Vec<_> = (0..selected_layers_collected.len()) - .map(|i| &selected_layers_collected.get(i).unwrap_or(&empty_vector.as_slice())[..1]) - .collect(); - let incoming_parent_selected = previous_parents.contains(&[incoming_parent].as_slice()); + let Some(new_selected) = layer + .ancestors(document.metadata()) + .find(|ancestor| ancestor.parent(document.metadata()).is_some_and(|parent| document.metadata().selected_layers_contains(parent))) + else { + return; + }; - if incoming_parent_selected { - // Permutations of intersected layer - // Set the base layer path to the deepest layer that is currently selected - let intersected_layer_ancestors: Vec> = (1..=intersect_layer_path.len()).map(|i| intersect_layer_path[..i].to_vec()).rev().collect(); - let mut new_layer_path: Vec = intersected_layer_ancestors - .iter() - .filter_map(|permutation| selected_layers_collected.iter().find(|layer| **layer == *permutation).map(|_| permutation.to_vec())) - .flatten() - .collect(); - - // Append the sub layer to the base to create the deeper layer path - for path in intersect_layer_path { - if !new_layer_path.contains(path) { - new_layer_path.push(*path); - break; - } - } - - tool_data.layers_dragging.clear(); - tool_data.layers_dragging.push(new_layer_path.to_vec()); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![new_layer_path.to_vec()], - }); - - if selected_layers_collected.contains(&new_layer_path.as_slice()) { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); - } - } + responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![new_selected.to_node()] }); } -fn edit_layer_deepest_manipulation(intersect: &Layer, responses: &mut VecDeque) { - match &intersect.data { - LayerDataType::Shape(_) => { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); - } - LayerDataType::Layer(layer) if layer.as_vector_data().is_some() => { - if layer.network.nodes.values().any(|node| node.name == "Text") { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }); - responses.add(TextToolMessage::EditSelected); - } else { - responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); - } - } - _ => {} +fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document: &Document, responses: &mut VecDeque) { + if is_text_layer(layer, document) { + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }); + responses.add(TextToolMessage::EditSelected); + } else if is_shape_layer(layer, document) { + responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); } } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 862d05bd11..73e4d7827b 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -202,7 +202,7 @@ impl Fsm for SplineToolFsmState { .. } = tool_action_data; - let transform = document.document_legacy.metadata.document_to_viewport; + let transform = document.metadata().document_to_viewport; let ToolMessage::Spline(event) = event else { return 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 d8b41c2cee..fc9bf6ed36 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 @@ -47,11 +47,12 @@ impl<'a> MessageHandler> for TransformL let using_path_tool = tool_data.active_tool_type == ToolType::Path; let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::>(); + let selected_layers_n = document.metadata().selected_layers().collect::>(); let mut selected = Selected::new( &mut self.original_transforms, &mut self.pivot, - &selected_layers, + &selected_layers_n, responses, &document.document_legacy, Some(shape_editor), @@ -228,7 +229,7 @@ impl<'a> MessageHandler> for TransformL self.mouse_position = ipp.mouse.position; } SelectionChanged => { - let target_layers = document.document_legacy.metadata.selected_layers().collect(); + let target_layers = document.metadata().selected_layers().collect(); shape_editor.set_selected_layers(target_layers); } 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 d785f23527..4bf9e7a114 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,5 +1,5 @@ use crate::messages::frontend::utility_types::FrontendImageData; -use crate::messages::portfolio::document::node_graph::wrap_network_in_scope; +use crate::messages::portfolio::document::node_graph::{transform_utils, wrap_network_in_scope}; use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry}; use crate::messages::prelude::*; @@ -56,6 +56,7 @@ pub struct NodeRuntime { pub(crate) thumbnails: HashMap, pub(crate) click_targets: HashMap>, pub(crate) transforms: HashMap, + pub(crate) upstream_transforms: HashMap, canvas_cache: HashMap, SurfaceId>, } @@ -80,6 +81,7 @@ pub(crate) struct GenerationResponse { new_thumbnails: HashMap, new_click_targets: HashMap>, new_transforms: HashMap, + new_upstream_transforms: HashMap, } enum NodeGraphUpdate { @@ -119,6 +121,7 @@ impl NodeRuntime { canvas_cache: HashMap::new(), click_targets: HashMap::new(), transforms: HashMap::new(), + upstream_transforms: HashMap::new(), } } pub async fn run(&mut self) { @@ -147,13 +150,14 @@ impl NodeRuntime { let monitor_nodes = network .recursive_nodes() - .filter(|node| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>")) - .map(|node| node.path.clone().unwrap_or_default()) - .collect(); + .filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>")) + .map(|(_, node)| node.path.clone().unwrap_or_default()) + .collect::>(); let result = self.execute_network(&path, network, transform, viewport_resolution).await; let mut responses = VecDeque::new(); - self.update_thumbnails(&path, monitor_nodes, &mut responses); + self.update_thumbnails(&path, &monitor_nodes, &mut responses); + self.update_upstream_transforms(&path, &monitor_nodes, &mut responses); let response = GenerationResponse { generation_id, result, @@ -161,6 +165,7 @@ impl NodeRuntime { 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(), + new_upstream_transforms: self.upstream_transforms.clone(), }; self.sender.send_generation_response(response); } @@ -184,7 +189,10 @@ impl NodeRuntime { resolution: viewport_resolution, ..Default::default() }, + #[cfg(any(feature = "resvg", feature = "vello"))] export_format: graphene_core::application_io::ExportFormat::Canvas, + #[cfg(not(any(feature = "resvg", feature = "vello")))] + export_format: graphene_core::application_io::ExportFormat::Svg, }, image_frame: None, }; @@ -223,14 +231,14 @@ 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) { + pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: &[Vec], responses: &mut VecDeque) { let mut image_data: Vec<_> = Vec::new(); for node_path in monitor_nodes { let Some(node_id) = node_path.get(node_path.len() - 2).copied() else { warn!("Monitor node has invalid node id"); continue; }; - let Some(value) = self.executor.introspect(&node_path).flatten() else { + let Some(value) = self.executor.introspect(node_path).flatten() else { warn!("Failed to introspect monitor node for thumbnail"); continue; }; @@ -280,6 +288,24 @@ impl NodeRuntime { responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data }); } } + + pub fn update_upstream_transforms(&mut self, layer_path: &[LayerId], monitor_nodes: &[Vec], responses: &mut VecDeque) { + for node_path in monitor_nodes { + let Some(node_id) = node_path.get(node_path.len() - 2).copied() else { + warn!("Monitor node has invalid node id"); + continue; + }; + let Some(value) = self.executor.introspect(node_path).flatten() else { + warn!("Failed to introspect monitor node for upstream transforms"); + continue; + }; + let Some(graphic_element_data) = value.downcast_ref::() else { + warn!("Failed to downcast transform input to vector data"); + continue; + }; + self.upstream_transforms.insert(node_id, graphic_element_data.transform()); + } + } } pub fn introspect_node(path: &[NodeId]) -> Option> { NODE_RUNTIME @@ -470,9 +496,10 @@ impl NodeGraphExecutor { new_thumbnails, new_click_targets, new_transforms, + new_upstream_transforms, }) => { self.thumbnails = new_thumbnails; - document.metadata.update_transforms(new_transforms); + document.metadata.update_transforms(new_transforms, new_upstream_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())?; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index da38c7728b..f9ec284e6f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2876,9 +2876,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001481", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", - "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", + "version": "1.0.30001546", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", + "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", "dev": true, "funding": [ { @@ -8017,9 +8017,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001481", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", - "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", + "version": "1.0.30001546", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", + "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", "dev": true }, "chalk": { diff --git a/frontend/src/components/panels/LayerTree.svelte b/frontend/src/components/panels/LayerTree.svelte index 19a133c933..5ac91cf05c 100644 --- a/frontend/src/components/panels/LayerTree.svelte +++ b/frontend/src/components/panels/LayerTree.svelte @@ -282,8 +282,10 @@ const layerId = BigInt(item.layerId.toString()); path.push(layerId); - const mapping = layerCache.get(path.toString()); + const mapping = layerCache.get([path[path.length-1]].toString()); if (mapping) { + mapping.layerType = item.children.length >= 1 ? "Folder" : "Layer"; + mapping.path = new BigUint64Array(path); layers.push({ folderIndex: index, bottomLayer: index === folder.children.length - 1, diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index a93fb1831e..f42f52969e 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -648,6 +648,7 @@ style:--data-color={`var(--color-data-${node.primaryInput})`} style:--data-color-dim={`var(--color-data-${node.primaryInput}-dim)`} > + {node.primaryInput} data @@ -665,6 +666,7 @@ style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`} > + {node.primaryOutput.dataType} data {/if} @@ -677,11 +679,12 @@ style:--data-color={`var(--color-data-${stackDatainput.dataType})`} style:--data-color-dim={`var(--color-data-${stackDatainput.dataType}-dim)`} > + {stackDatainput.dataType} data
- {node.displayName} + {node.displayName}
@@ -738,6 +741,7 @@ style:--data-color={`var(--color-data-${node.primaryInput})`} style:--data-color-dim={`var(--color-data-${node.primaryInput}-dim)`} > + {node.primaryInput} data {/if} @@ -752,6 +756,7 @@ style:--data-color={`var(--color-data-${parameter.dataType})`} style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`} > + {parameter.dataType} data {/if} @@ -769,6 +774,7 @@ style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`} > + {node.primaryOutput.dataType} data {/if} @@ -782,6 +788,7 @@ style:--data-color={`var(--color-data-${parameter.dataType})`} style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`} > + {parameter.dataType} data {/each} diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 935fa91bf6..12fa586c20 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -652,7 +652,7 @@ impl JsEditorHandle { #[wasm_bindgen(js_name = selectNodes)] pub fn select_nodes(&self, nodes: Option>) { let nodes = nodes.unwrap_or_default(); - let message = NodeGraphMessage::SelectNodes { nodes }; + let message = NodeGraphMessage::SetSelectNodes { nodes }; self.dispatch(message); } diff --git a/node-graph/gcore/src/vector/subpath.rs b/node-graph/gcore/src/vector/subpath.rs index 9bdacba59f..2d4eb8b06b 100644 --- a/node-graph/gcore/src/vector/subpath.rs +++ b/node-graph/gcore/src/vector/subpath.rs @@ -47,13 +47,13 @@ impl Subpath { } /// Convert to the legacy Subpath from the `bezier_rs::Subpath`. - pub fn from_bezier_rs(value: &[bezier_rs::Subpath]) -> Self { + pub fn from_bezier_rs<'a, Subpath: core::borrow::Borrow<&'a bezier_rs::Subpath>>(value: impl IntoIterator) -> Self { let mut groups = IdBackedVec::new(); - for subpath in value { - for group in subpath.manipulator_groups() { + for subpath in value.into_iter() { + for group in subpath.borrow().manipulator_groups() { groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle)); } - if subpath.closed() { + if subpath.borrow().closed() { groups.push(ManipulatorGroup::closed()); } } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 5fd1ece637..92febc1450 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -59,8 +59,7 @@ impl DocumentNode { fn resolve_proto_node(mut self) -> ProtoNode { assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {:#?} with no inputs", self); - let DocumentNodeImplementation::Unresolved(fqn) = self.implementation - else { + let DocumentNodeImplementation::Unresolved(fqn) = self.implementation else { unreachable!("tried to resolve not flattened node on resolved node {:?}", self); }; let (input, mut args) = if let Some(ty) = self.manual_composition { @@ -245,6 +244,13 @@ impl NodeInput { None } } + pub fn as_node(&self) -> Option { + if let NodeInput::Node { node_id, .. } = self { + Some(*node_id) + } else { + None + } + } } #[derive(Clone, Debug, PartialEq, Hash, DynAny)] @@ -905,22 +911,22 @@ impl NodeNetwork { /// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested. pub fn recursive_nodes(&self) -> RecursiveNodeIter { - let nodes = self.nodes.values().collect(); + let nodes = self.nodes.iter().collect(); RecursiveNodeIter { nodes } } } /// An iterator over all [`DocumentNode`]s, including ones that are deeply nested. pub struct RecursiveNodeIter<'a> { - nodes: Vec<&'a DocumentNode>, + nodes: Vec<(&'a NodeId, &'a DocumentNode)>, } impl<'a> Iterator for RecursiveNodeIter<'a> { - type Item = &'a DocumentNode; + type Item = (&'a NodeId, &'a DocumentNode); fn next(&mut self) -> Option { let node = self.nodes.pop()?; - if let DocumentNodeImplementation::Network(network) = &node.implementation { - self.nodes.extend(network.nodes.values()); + if let DocumentNodeImplementation::Network(network) = &node.1.implementation { + self.nodes.extend(network.nodes.iter()); } Some(node) } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index fc379fe6f8..15f4a00398 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -359,6 +359,6 @@ async fn render_node<'a: 'input, F: Future>( }; RenderOutput::CanvasFrame(frame.into()) } - _ => todo!("Non svg render output"), + _ => todo!("Non svg render output for {output_format:?}"), } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 481316adb1..fea973ae64 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -328,6 +328,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: VectorData, 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: []),