From 58660f554825639f8688807a97cb4d1fbfd38770 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:17:14 +0000 Subject: [PATCH] Allow groups to work with the node graph (#1452) * Initial groups * Improve graph arangement * Fix selecting nested layers * Code review pass * Change log --------- Co-authored-by: Keavon Chambers --- document-legacy/src/document_metadata.rs | 34 ++- document-legacy/src/response.rs | 6 - editor/src/dispatcher.rs | 12 +- .../portfolio/document/document_message.rs | 10 +- .../document/document_message_handler.rs | 220 ++++++++---------- .../node_graph/graph_operation_message.rs | 8 + .../graph_operation_message_handler.rs | 114 +++++++-- .../document/node_graph/node_graph_message.rs | 15 +- .../node_graph/node_graph_message_handler.rs | 29 ++- .../document/utility_types/clipboards.rs | 11 +- .../messages/portfolio/portfolio_message.rs | 7 +- .../portfolio/portfolio_message_handler.rs | 140 +++++------ .../graph_modification_utils.rs | 4 +- .../messages/tool/tool_messages/path_tool.rs | 8 +- .../tool/tool_messages/select_tool.rs | 14 +- .../messages/tool/tool_messages/text_tool.rs | 4 +- editor/src/node_graph_executor.rs | 31 ++- frontend/wasm/src/editor_api.rs | 11 +- node-graph/graph-craft/src/document.rs | 25 +- 19 files changed, 397 insertions(+), 306 deletions(-) diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs index c2380c9ae3..742862e136 100644 --- a/document-legacy/src/document_metadata.rs +++ b/document-legacy/src/document_metadata.rs @@ -77,6 +77,7 @@ impl DocumentMetadata { self.structure.entry(node_identifier).or_default() } + /// Layers excluding ones that are children of other layers in the list. pub fn shallowest_unique_layers(&self, layers: impl Iterator) -> Vec> { let mut sorted_layers = layers .map(|layer| { @@ -90,6 +91,34 @@ impl DocumentMetadata { sorted_layers.dedup_by(|a, b| a.starts_with(b)); sorted_layers } + + /// Ancestor that is shared by all layers and that is deepest (more nested). May be the root layer. + pub fn deepest_common_ancestor(&self, layers: impl Iterator) -> LayerNodeIdentifier { + layers + .map(|layer| { + let mut layer_path = layer.ancestors(self).skip(1).collect::>(); + layer_path.reverse(); + layer_path + }) + .reduce(|mut a, b| { + a.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len()))); + a + }) + .and_then(|path| path.last().copied()) + .unwrap_or(LayerNodeIdentifier::ROOT) + } + + /// Filter out non folder layers + pub fn folders<'a>(&'a self, layers: impl Iterator + 'a) -> impl Iterator + 'a { + layers.filter(|layer| layer.has_children(self)) + } + + /// Folders sorted from most nested to least nested + pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator) -> Vec { + let mut folders: Vec<_> = self.folders(layers).collect(); + folders.sort_by_cached_key(|a| std::cmp::Reverse(a.ancestors(self).count())); + folders + } } // selected layer modifications @@ -146,7 +175,7 @@ impl DocumentMetadata { } fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a DocumentNode, NodeId)> { - graph.primary_flow_from_opt(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer") + graph.primary_flow_from_node(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer") } fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> 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))) @@ -178,7 +207,7 @@ impl DocumentMetadata { } fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool { - network.primary_flow_from_opt(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard") + network.primary_flow_from_node(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard") } // click targets @@ -299,6 +328,7 @@ impl LayerNodeIdentifier { } /// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node + #[track_caller] pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self { debug_assert!( is_layer_node(node_id, network), diff --git a/document-legacy/src/response.rs b/document-legacy/src/response.rs index 05c901277e..e681246433 100644 --- a/document-legacy/src/response.rs +++ b/document-legacy/src/response.rs @@ -22,11 +22,6 @@ pub enum DocumentResponse { LayerChanged { path: Vec, }, - MoveSelectedLayersTo { - folder_path: Vec, - insert_index: isize, - reverse_index: bool, - }, DeletedSelectedManipulatorPoints, } @@ -39,7 +34,6 @@ impl fmt::Display for DocumentResponse { DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"), DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"), DocumentResponse::DeletedSelectedManipulatorPoints { .. } => write!(f, "DeletedSelectedManipulatorPoints"), - DocumentResponse::MoveSelectedLayersTo { .. } => write!(f, "MoveSelectedLayersTo"), } } } diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 7ca75368ec..a4632fc27a 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -302,7 +302,7 @@ mod test { editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().document_legacy.clone(); @@ -341,7 +341,7 @@ mod test { editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); @@ -407,12 +407,12 @@ mod test { editor.handle_message(DocumentMessage::DeleteSelectedLayers); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); @@ -479,12 +479,12 @@ mod test { editor.draw_rect(0., 800., 12., 200.); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); editor.handle_message(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: vec![], + parent: LayerNodeIdentifier::ROOT, insert_index: -1, }); diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index fa67c9adc5..9999b754ed 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::prelude::*; use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::blend_mode::BlendMode; use document_legacy::layers::style::ViewMode; use document_legacy::LayerId; @@ -106,9 +107,8 @@ pub enum DocumentMessage { affected_layer_path: Vec, }, MoveSelectedLayersTo { - folder_path: Vec, + parent: LayerNodeIdentifier, insert_index: isize, - reverse_index: bool, }, NudgeSelectedLayers { delta_x: f64, @@ -169,6 +169,9 @@ pub enum DocumentMessage { SetOverlaysVisibility { visible: bool, }, + SetRangeSelectionLayer { + new_layer: Option, + }, SetSelectedLayers { replacement_selected_layers: Vec>, }, @@ -189,9 +192,6 @@ pub enum DocumentMessage { }, Undo, UndoFinished, - UngroupLayers { - folder_path: Vec, - }, UngroupSelectedLayers, UpdateDocumentTransform { transform: glam::DAffine2, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 79513eb103..3f13a3c4c0 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -55,7 +55,7 @@ pub struct DocumentMessageHandler { #[serde(with = "vectorize_layer_metadata")] pub layer_metadata: HashMap, LayerMetadata>, - layer_range_selection_reference: Vec, + layer_range_selection_reference: Option, navigation_handler: NavigationMessageHandler, #[serde(skip)] @@ -88,7 +88,7 @@ impl Default for DocumentMessageHandler { undo_in_progress: false, layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(), - layer_range_selection_reference: Vec::new(), + layer_range_selection_reference: None, navigation_handler: NavigationMessageHandler::default(), overlays_message_handler: OverlaysMessageHandler::default(), @@ -134,15 +134,6 @@ impl MessageHandler> for DocumentMessageHand self.layer_metadata.remove(path); } DocumentResponse::LayerChanged { path } => responses.add(LayerChanged { affected_layer_path: path.clone() }), - DocumentResponse::MoveSelectedLayersTo { - folder_path, - insert_index, - reverse_index, - } => responses.add(MoveSelectedLayersTo { - folder_path: folder_path.clone(), - insert_index: *insert_index, - reverse_index: *reverse_index, - }), DocumentResponse::CreatedLayer { .. } => { unimplemented!("We should no longer be creating layers in the document and should instead be using the node graph.") } @@ -318,7 +309,7 @@ impl MessageHandler> for DocumentMessageHand } DeselectAllLayers => { responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] }); - self.layer_range_selection_reference.clear(); + self.layer_range_selection_reference = None; } DirtyRenderDocument => { // Mark all non-overlay caches as dirty @@ -350,7 +341,7 @@ impl MessageHandler> for DocumentMessageHand DuplicateSelectedLayers => { self.backup(responses); responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] }); - self.layer_range_selection_reference.clear(); + self.layer_range_selection_reference = None; for path in self.selected_layers_sorted() { responses.add(DocumentOperation::DuplicateLayer { path: path.to_vec() }); } @@ -439,30 +430,26 @@ impl MessageHandler> for DocumentMessageHand } GroupSelectedLayers => { // TODO: Add code that changes the insert index of the new folder based on the selected layer - let mut new_folder_path = self.document_legacy.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec(); + let parent = self.metadata().deepest_common_ancestor(self.metadata().selected_layers()); - // Required for grouping parent folders with their own children - if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) { - new_folder_path.remove(new_folder_path.len() - 1); - } - - new_folder_path.push(generate_uuid()); + let folder_id = generate_uuid(); responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); responses.add(DocumentMessage::DeleteSelectedLayers); - responses.add(DocumentOperation::CreateFolder { - path: new_folder_path.clone(), + + responses.add(GraphOperationMessage::NewCustomLayer { + id: folder_id, + nodes: HashMap::new(), + parent, insert_index: -1, }); - responses.add(DocumentMessage::ToggleLayerExpansion { layer_path: new_folder_path.clone() }); responses.add(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path: new_folder_path.clone(), + parent: LayerNodeIdentifier::new_unchecked(folder_id), insert_index: -1, }); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![new_folder_path], - }); + + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] }); } ImaginateClear { layer_path } => responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }), ImaginateGenerate { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }), @@ -496,25 +483,21 @@ impl MessageHandler> for DocumentMessageHand responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }); self.update_layer_tree_options_bar_widgets(responses, &render_data); } - MoveSelectedLayersTo { - folder_path, - insert_index, - reverse_index, - } => { - let selected_layers = self.selected_layers().collect::>(); + MoveSelectedLayersTo { parent, insert_index } => { + let selected_layers = self.metadata().selected_layers().collect::>(); - // Prevent trying to insert into self - if selected_layers.iter().any(|layer| folder_path.starts_with(layer)) { + // Disallow trying to insert into self + if selected_layers.iter().any(|&layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer)) { return; } - let insert_index = self.update_insert_index(&selected_layers, &folder_path, insert_index, reverse_index).unwrap(); + let insert_index = self.update_insert_index(&selected_layers, parent, insert_index).unwrap(); responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); responses.add(DocumentMessage::DeleteSelectedLayers); responses.add(PortfolioMessage::PasteIntoFolder { clipboard: Clipboard::Internal, - folder_path, + parent, insert_index, }); } @@ -700,42 +683,47 @@ impl MessageHandler> for DocumentMessageHand self.selected_layers_reorder(relative_index_offset, responses); } SelectLayer { layer_path, ctrl, shift } => { - let mut paths = vec![]; - let last_selection_exists = !self.layer_range_selection_reference.is_empty(); + let clicked_node = *layer_path.last().expect("Cannot select root"); + let layer = LayerNodeIdentifier::new(clicked_node, self.network()); + + let mut nodes = vec![]; // If we have shift pressed and a layer already selected then fill the range - if shift && last_selection_exists { + if let Some(last_selected) = self.layer_range_selection_reference.filter(|_| shift) { + nodes.push(last_selected.to_node()); + nodes.push(clicked_node); + // Fill the selection range - self.layer_metadata - .iter() - .filter(|(target, _)| self.document_legacy.layer_is_between(target, &layer_path, &self.layer_range_selection_reference)) - .for_each(|(layer_path, _)| { - paths.push(layer_path.clone()); - }); + self.metadata() + .all_layers() + .skip_while(|&node| node != layer && node != last_selected) + .skip(1) + .take_while(|&node| node != layer && node != last_selected) + .for_each(|node| nodes.push(node.to_node())); } else { if ctrl { // Toggle selection when holding ctrl - let layer = self.layer_metadata_mut(&layer_path); - layer.selected = !layer.selected; - responses.add(LayerChanged { - affected_layer_path: layer_path.clone(), - }); + if self.metadata().selected_layers_contains(layer) { + responses.add_front(NodeGraphMessage::SelectedNodesRemove { nodes: vec![clicked_node] }); + } else { + responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![clicked_node] }); + } responses.add(BroadcastEvent::SelectionChanged); } else { - paths.push(layer_path.clone()); + nodes.push(clicked_node); } // Set our last selection reference - self.layer_range_selection_reference = layer_path; + self.layer_range_selection_reference = Some(layer); } // Don't create messages for empty operations - if !paths.is_empty() { + if !nodes.is_empty() { // Add or set our selected layers if ctrl { - responses.add_front(AddSelectedLayers { additional_layers: paths }); + responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes }); } else { - responses.add_front(SetSelectedLayers { replacement_selected_layers: paths }); + responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes }); } } } @@ -802,6 +790,9 @@ impl MessageHandler> for DocumentMessageHand responses.add(OverlaysMessage::ClearAllOverlays); responses.add(OverlaysMessage::Rerender); } + SetRangeSelectionLayer { new_layer } => { + self.layer_range_selection_reference = new_layer; + } SetSelectedLayers { replacement_selected_layers } => { let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected); selected.for_each(|(path, layer_metadata)| { @@ -854,36 +845,28 @@ impl MessageHandler> for DocumentMessageHand responses.add(UndoFinished); } UndoFinished => self.undo_in_progress = false, - UngroupLayers { folder_path } => { - // Select all the children of the folder - let select = self.document_legacy.folder_children_paths(&folder_path); - - let message_buffer: [Message; 4] = [ - // Select them - DocumentMessage::SetSelectedLayers { replacement_selected_layers: select }.into(), - // Copy them - PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into(), - // Paste them into the folder above - PortfolioMessage::PasteIntoFolder { - clipboard: Clipboard::Internal, - folder_path: folder_path[..folder_path.len() - 1].to_vec(), - insert_index: -1, - } - .into(), - // Delete the parent folder - DocumentMessage::DeleteLayer { layer_path: folder_path }.into(), - ]; - - // Push these messages in reverse due to push_front - for message in message_buffer.into_iter().rev() { - responses.add_front(message); - } - } UngroupSelectedLayers => { responses.add(DocumentMessage::StartTransaction); - let folder_paths = self.document_legacy.sorted_folders_by_depth(self.selected_layers()); - for folder_path in folder_paths { - responses.add(DocumentMessage::UngroupLayers { folder_path: folder_path.to_vec() }); + + let folder_paths = self.metadata().folders_sorted_by_most_nested(self.metadata().selected_layers()); + + for folder in folder_paths { + // Select all the children of the folder + responses.add(NodeGraphMessage::SelectedNodesSet { + nodes: folder.children(self.metadata()).map(LayerNodeIdentifier::to_node).collect(), + }); + + // Copy them + responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); + + // Paste them into the folder above + responses.add(PortfolioMessage::PasteIntoFolder { + clipboard: Clipboard::Internal, + parent: folder.parent(self.metadata()).unwrap_or(LayerNodeIdentifier::ROOT), + insert_index: -1, + }); + // Delete the parent folder + responses.add(GraphOperationMessage::DeleteLayer { id: folder.to_node() }); } responses.add(DocumentMessage::CommitTransaction); } @@ -1415,12 +1398,11 @@ impl DocumentMessageHandler { /// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index). /// /// This function updates the insert index so that it points to the same place after the specified `layers` are deleted. - fn update_insert_index(&self, layers: &[&[LayerId]], path: &[LayerId], insert_index: isize, reverse_index: bool) -> Result { - let folder = self.document_legacy.folder(path)?; - let insert_index = if reverse_index { folder.layer_ids.len() as isize - insert_index } else { insert_index }; - let layer_ids_above = if insert_index < 0 { &folder.layer_ids } else { &folder.layer_ids[..(insert_index as usize)] }; + fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> Result { + let layer_ids_above = parent.children(self.metadata()).take(if insert_index < 0 { usize::MAX } else { insert_index as usize }); + let new_insert_index = layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as isize; - Ok(insert_index - layer_ids_above.iter().filter(|layer_id| layers.iter().any(|x| *x == [path, &[**layer_id]].concat())).count() as isize) + Ok(new_insert_index) } /// Calculates the bounding box of all layers in the document @@ -1739,53 +1721,39 @@ impl DocumentMessageHandler { pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque) { self.backup(responses); - let all_layer_paths = self.all_layers_sorted(); - let selected_layers = self.selected_layers_sorted(); + let mut selected_layers = self.metadata().selected_layers(); let first_or_last_selected_layer = match relative_index_offset.signum() { - -1 => selected_layers.first(), + -1 => selected_layers.next(), 1 => selected_layers.last(), _ => panic!("selected_layers_reorder() must be given a non-zero value"), }; - if let Some(pivot_layer) = first_or_last_selected_layer { - let sibling_layer_paths: Vec<_> = all_layer_paths - .iter() - .filter(|layer| { - // Check if this is a sibling of the pivot layer - // TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool` - let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1]; - layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len() - }) - .collect(); + let Some(pivot_layer) = first_or_last_selected_layer else { + return; + }; + let Some(parent) = pivot_layer.parent(self.metadata()) else { + return; + }; - // TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize` - let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer); + let sibling_layer_paths: Vec<_> = parent.children(self.metadata()).collect(); + let Some(pivot_index) = sibling_layer_paths.iter().position(|path| *path == pivot_layer) else { + return; + }; - if let Some(pivot_index) = pivot_index_among_siblings { - let max = sibling_layer_paths.len() as i64 - 1; - let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize; + let max = sibling_layer_paths.len() as i64 - 1; + let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize; - let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index); + let Some(&neighbor) = sibling_layer_paths.get(insert_index) else { + return; + }; + let Some(neighbor_index) = sibling_layer_paths.iter().position(|path| *path == neighbor) else { + return; + }; - // TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }` - if let Some(neighbor_path) = existing_layer_to_insert_beside { - let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder"); + // If moving down, insert below this layer. If moving up, insert above this layer. + let insert_index = if relative_index_offset < 0 { neighbor_index } else { neighbor_index + 1 } as isize; - if let Some(folder) = self.document_legacy.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) { - let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize; - - // If moving down, insert below this layer. If moving up, insert above this layer. - let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 }; - - responses.add(DocumentMessage::MoveSelectedLayersTo { - folder_path: folder_path.to_vec(), - insert_index, - reverse_index: false, - }); - } - } - } - } + responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index }); } } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs index c24a0961e8..6b63882485 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs @@ -1,6 +1,8 @@ use crate::messages::prelude::*; use bezier_rs::Subpath; +use document_legacy::document_metadata::LayerNodeIdentifier; +use graph_craft::document::DocumentNode; use graph_craft::document::NodeId; use graphene_core::raster::ImageFrame; use graphene_core::text::Font; @@ -65,6 +67,12 @@ pub enum GraphOperationMessage { id: NodeId, image_frame: ImageFrame, }, + NewCustomLayer { + id: NodeId, + nodes: HashMap, + parent: LayerNodeIdentifier, + insert_index: isize, + }, NewVectorLayer { id: NodeId, subpaths: 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 2f3561dd10..2954a6d494 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 @@ -92,30 +92,47 @@ impl<'a> ModifyInputsContext<'a> { Some(new_id) } - pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize) -> Option { + pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize, skip_layer_nodes: usize) -> Option { assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing layer"); - let output = NodeOutput::new(output_node_id, input_index); + let mut output = NodeOutput::new(output_node_id, input_index); + let mut sibling_layer = None; + let mut shift = IVec2::new(0, 3); // Locate the node output of the first sibling layer to the new layer - let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] { + if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] { let sibling_node = &self.network.nodes.get(node_id)?; let node_id = *node_id; let output_index = *output_index; - let sibling_layer = if sibling_node.name == "Layer" { + if sibling_node.name == "Layer" { // There is already a layer node - NodeOutput::new(node_id, 0) + sibling_layer = Some(NodeOutput::new(node_id, 0)); } else { // The user has connected another node to the output. Insert a layer node between the output and the node. let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); let node_id = self.insert_between(generate_uuid(), NodeOutput::new(node_id, output_index), output, node, 0, 0, IVec2::new(-8, 0))?; - NodeOutput::new(node_id, 0) - }; + sibling_layer = Some(NodeOutput::new(node_id, 0)); + } - let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); - self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(0, 3)) + // Skip some layer nodes + for _ in 0..skip_layer_nodes { + if let Some(old_sibling) = &sibling_layer { + output = NodeOutput::new(old_sibling.node_id, 7); + sibling_layer = self.network.nodes.get(&old_sibling.node_id)?.inputs[7].as_node().map(|node| NodeOutput::new(node, 0)); + shift = IVec2::new(0, 3); + } + } + + // Insert at top of stack } else { - let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node(); - self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-5, 3)) + shift = IVec2::new(-8, 3); + } + + // Create node + let layer_node = resolve_document_node_type("Layer").expect("Layer node").default_document_node(); + let new_id = if let Some(sibling_layer) = sibling_layer { + self.insert_between(new_id, sibling_layer, output, layer_node, 7, 0, shift) + } else { + self.insert_node_before(new_id, output.node_id, output.node_output_index, layer_node, shift) }; // Update the document metadata structure @@ -262,7 +279,7 @@ impl<'a> ModifyInputsContext<'a> { /// Changes the inputs of a specific node 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); + let existing_node_id = self.network.primary_flow_from_node(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 { @@ -285,7 +302,7 @@ 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(); + let existing_nodes: Vec<_> = self.network.primary_flow_from_node(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); } @@ -460,6 +477,7 @@ impl<'a> ModifyInputsContext<'a> { LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata); let new_input = node.inputs[7].clone(); + let deleted_position = node.metadata.position; for post_node in self.outwards_links.get(&id).unwrap_or(&Vec::new()) { let Some(node) = self.network.nodes.get_mut(post_node) else { @@ -476,7 +494,7 @@ impl<'a> ModifyInputsContext<'a> { } let mut delete_nodes = vec![id]; - for (_node, id) in self.network.primary_flow_from_opt(Some(id)) { + for (_node, id) in self.network.primary_flow_from_node(Some(id)) { if self.outwards_links.get(&id).is_some_and(|outwards| outwards.len() == 1) { delete_nodes.push(id); } @@ -485,6 +503,16 @@ impl<'a> ModifyInputsContext<'a> { for node_id in &delete_nodes { self.network.nodes.remove(node_id); } + + if let Some(node_id) = new_input.as_node() { + if let Some(shift) = self.network.nodes.get(&node_id).map(|node| deleted_position - node.metadata.position) { + for node_id in self.network.all_dependencies(node_id).map(|(_, id)| id).collect::>() { + let Some(node) = self.network.nodes.get_mut(&node_id) else { continue }; + node.metadata.position += shift; + } + } + } + self.responses.add(self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id))); self.responses.add(DocumentMessage::DocumentStructureChanged); @@ -567,25 +595,75 @@ impl MessageHandler { let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); - if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) { + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) { modify_inputs.insert_artboard(artboard, layer); } } GraphOperationMessage::NewBitmapLayer { id, image_frame } => { let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); - if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) { + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) { modify_inputs.insert_image_data(image_frame, layer); } } + GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => { + trace!("Inserting new layer {id} as a child of {parent:?} at index {insert_index}"); + + let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); + + let skip_layer_nodes = if insert_index < 0 { (-1 - insert_index) as usize } else { insert_index as usize }; + + let output_node_id = if parent == LayerNodeIdentifier::ROOT { + modify_inputs.network.original_outputs()[0].node_id + } else { + parent.to_node() + }; + + if let Some(layer) = modify_inputs.create_layer(id, output_node_id, 0, skip_layer_nodes) { + let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, crate::application::generate_uuid())).collect(); + + let shift = nodes + .get(&0) + .and_then(|node| { + modify_inputs + .network + .nodes + .get(&layer) + .map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0)) + }) + .unwrap_or_default(); + + for (old_id, mut document_node) in nodes { + // Shift copied node + document_node.metadata.position += shift; + + // Get the new, non-conflicting id + let node_id = *new_ids.get(&old_id).unwrap(); + document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids); + + // Insert node into network + modify_inputs.network.nodes.insert(node_id, document_node); + } + + if let Some(layer_node) = modify_inputs.network.nodes.get_mut(&layer) { + if let Some(&input) = new_ids.get(&0) { + layer_node.inputs[0] = NodeInput::node(input, 0) + } + } + + modify_inputs.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); + } + + document.metadata.load_structure(&document.document_network); + } GraphOperationMessage::NewVectorLayer { id, subpaths } => { let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); - if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) { + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) { modify_inputs.insert_vector_data(subpaths, layer); } } GraphOperationMessage::NewTextLayer { id, text, font, size } => { let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses); - if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) { + if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) { modify_inputs.insert_text(text, font, size, layer); } } 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 7f516adbbc..42e9361b9f 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 @@ -9,9 +9,6 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput}; pub enum NodeGraphMessage { // Messages Init, - AddSelectNodes { - nodes: Vec, - }, SelectedNodesUpdated, CloseNodeGraph, ConnectNodesByLink { @@ -67,6 +64,15 @@ pub enum NodeGraphMessage { serialized_nodes: String, }, RunDocumentGraph, + SelectedNodesAdd { + nodes: Vec, + }, + SelectedNodesRemove { + nodes: Vec, + }, + SelectedNodesSet { + nodes: Vec, + }, SendGraph { should_rerender: bool, }, @@ -86,9 +92,6 @@ pub enum NodeGraphMessage { input_index: usize, value: TaggedValue, }, - SetSelectedNodes { - 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 5cdd285949..ce8786e0de 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 @@ -417,14 +417,14 @@ impl NodeGraphMessageHandler { } /// Gets the default node input based on the node name and the input index - fn default_node_input(name: String, index: usize) -> Option { + pub fn default_node_input(name: String, index: usize) -> Option { resolve_document_node_type(&name) .and_then(|node| node.inputs.get(index)) .map(|input: &DocumentInputType| input.default.clone()) } /// Returns an iterator of nodes to be copied and their ids, excluding output and input nodes - fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap) -> impl Iterator + 'a { + pub fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap) -> impl Iterator + 'a { new_ids .iter() .filter(|&(&id, _)| !network.outputs_contain(id)) @@ -455,12 +455,15 @@ impl<'a> MessageHandler> for NodeGrap 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); + if document.metadata.selected_layers().count() <= 1 { + responses.add(DocumentMessage::SetRangeSelectionLayer { + new_layer: document.metadata.selected_layers().next(), + }); + } + responses.add(NodeGraphMessage::RunDocumentGraph); } NodeGraphMessage::CloseNodeGraph => {} NodeGraphMessage::ConnectNodesByLink { @@ -739,11 +742,21 @@ impl<'a> MessageHandler> for NodeGrap } let nodes = new_ids.values().copied().collect(); - responses.add(NodeGraphMessage::SetSelectedNodes { nodes }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path: Vec::new() }), + NodeGraphMessage::SelectedNodesAdd { nodes } => { + responses.add(document.metadata.add_selected_nodes(nodes)); + } + NodeGraphMessage::SelectedNodesRemove { nodes } => { + responses.add(document.metadata.retain_selected_nodes(|node| !nodes.contains(node))); + } + NodeGraphMessage::SelectedNodesSet { nodes } => { + responses.add(document.metadata.set_selected_nodes(nodes)); + responses.add(PropertiesPanelMessage::ResendActiveProperties); + } NodeGraphMessage::SendGraph { should_rerender } => { if let Some(network) = document.document_network.nested_network(&self.network) { Self::send_graph(network, &self.layer_path, responses); @@ -815,10 +828,6 @@ impl<'a> MessageHandler> for NodeGrap } } } - NodeGraphMessage::SetSelectedNodes { nodes } => { - responses.add(document.metadata.set_selected_nodes(nodes)); - responses.add(PropertiesPanelMessage::ResendActiveProperties); - } NodeGraphMessage::ShiftNode { node_id } => { let Some(network) = document.document_network.nested_network_mut(&self.network) else { warn!("No network"); diff --git a/editor/src/messages/portfolio/document/utility_types/clipboards.rs b/editor/src/messages/portfolio/document/utility_types/clipboards.rs index c081248d7e..c4f16984e2 100644 --- a/editor/src/messages/portfolio/document/utility_types/clipboards.rs +++ b/editor/src/messages/portfolio/document/utility_types/clipboards.rs @@ -1,8 +1,8 @@ -use super::layer_panel::LayerMetadata; - -use document_legacy::layers::layer_info::Layer; +use graph_craft::document::DocumentNode; +use graph_craft::document::NodeId; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; #[repr(u8)] #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)] @@ -18,6 +18,7 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct CopyBufferEntry { - pub layer: Layer, - pub layer_metadata: LayerMetadata, + pub nodes: HashMap, + pub selected: bool, + pub collapsed: bool, } diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index b38c1e3687..646a5cf447 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,6 +1,7 @@ use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::LayerId; use graph_craft::document::NodeId; use graphene_core::text::Font; @@ -87,13 +88,9 @@ pub enum PortfolioMessage { document_is_saved: bool, document_serialized_content: String, }, - // TODO: Paste message is unused, delete it? - Paste { - clipboard: Clipboard, - }, PasteIntoFolder { clipboard: Clipboard, - folder_path: Vec, + parent: LayerNodeIdentifier, insert_index: isize, }, PasteSerializedData { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 123ba8b191..ce95d7ee2c 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -12,7 +12,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup}; use crate::node_graph_executor::NodeGraphExecutor; use document_legacy::layers::style::RenderData; -use document_legacy::Operation as DocumentOperation; use graph_craft::document::NodeId; use graphene_core::text::Font; @@ -166,30 +165,49 @@ impl MessageHandler { // We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self` - if let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) { - let copy_val = |buffer: &mut Vec| { - for layer_path in active_document.selected_layers_without_children() { - match (active_document.document_legacy.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) { - (Ok(layer), layer_metadata) => { - buffer.push(CopyBufferEntry { layer, layer_metadata }); - } - (Err(e), _) => warn!("Could not access selected layer {layer_path:?}: {e:?}"), - } - } - }; + let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) else { + return; + }; - if clipboard == Clipboard::Device { - let mut buffer = Vec::new(); - copy_val(&mut buffer); - let mut copy_text = String::from("graphite/layer: "); - copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste"); + let copy_val = |buffer: &mut Vec| { + for layer_path in active_document.metadata().shallowest_unique_layers(active_document.metadata().selected_layers()) { + let Some(layer) = layer_path.last().copied() else { + continue; + }; - responses.add(FrontendMessage::TriggerTextCopy { copy_text }); - } else { - let copy_buffer = &mut self.copy_buffer; - copy_buffer[clipboard as usize].clear(); - copy_val(&mut copy_buffer[clipboard as usize]); + let node = layer.to_node(); + let Some(node) = active_document.network().nodes.get(&node).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else { + continue; + }; + + buffer.push(CopyBufferEntry { + nodes: NodeGraphMessageHandler::copy_nodes( + active_document.network(), + &active_document + .network() + .all_dependencies(node) + .enumerate() + .map(|(index, (_, node_id))| (node_id, index as NodeId)) + .collect(), + ) + .collect(), + selected: active_document.metadata().selected_layers_contains(layer), + collapsed: false, + }); } + }; + + if clipboard == Clipboard::Device { + let mut buffer = Vec::new(); + copy_val(&mut buffer); + let mut copy_text = String::from("graphite/layer: "); + copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste"); + + responses.add(FrontendMessage::TriggerTextCopy { copy_text }); + } else { + let copy_buffer = &mut self.copy_buffer; + copy_buffer[clipboard as usize].clear(); + copy_val(&mut copy_buffer[clipboard as usize]); } } PortfolioMessage::Cut { clipboard } => { @@ -364,47 +382,20 @@ impl MessageHandler { - let shallowest_common_folder = self.active_document().map(|document| { - document - .document_legacy - .shallowest_common_folder(document.selected_layers()) - .expect("While pasting, the selected layers did not exist while attempting to find the appropriate folder path for insertion") - }); - - if let Some(folder) = shallowest_common_folder { - responses.add(DocumentMessage::DeselectAllLayers); - responses.add(DocumentMessage::StartTransaction); - responses.add(PortfolioMessage::PasteIntoFolder { - clipboard, - folder_path: folder.to_vec(), - insert_index: -1, - }); - responses.add(DocumentMessage::CommitTransaction); - } - } - PortfolioMessage::PasteIntoFolder { - clipboard, - folder_path: path, - insert_index, - } => { + PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => { let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| { - if let Some(document) = self.active_document() { - trace!("Pasting into folder {path:?} as index: {insert_index}"); - let destination_path = [path.to_vec(), vec![generate_uuid()]].concat(); - - responses.add_front(DocumentMessage::UpdateLayerMetadata { - layer_path: destination_path.clone(), - layer_metadata: entry.layer_metadata, - }); - document.load_layer_resources(responses); - responses.add_front(DocumentOperation::InsertLayer { - layer: Box::new(entry.layer.clone()), - destination_path, + if self.active_document().is_some() { + trace!("Pasting into folder {parent:?} as index: {insert_index}"); + let id = generate_uuid(); + responses.add(GraphOperationMessage::NewCustomLayer { + id, + nodes: entry.nodes.clone(), + parent, insert_index, - duplicating: false, }); + if entry.selected { + responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); + } } }; @@ -421,27 +412,23 @@ impl MessageHandler { if let Some(document) = self.active_document() { if let Ok(data) = serde_json::from_str::>(&data) { - let shallowest_common_folder = document - .document_legacy - .shallowest_common_folder(document.selected_layers()) - .expect("While pasting from serialized, the selected layers did not exist while attempting to find the appropriate folder path for insertion"); + let parent = document.metadata().deepest_common_ancestor(document.metadata().selected_layers()); + responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::StartTransaction); - for entry in data.iter().rev() { - let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat(); - + for entry in data.into_iter().rev() { document.load_layer_resources(responses); - responses.add(DocumentOperation::InsertLayer { - layer: Box::new(entry.layer.clone()), - destination_path: destination_path.clone(), + let id = generate_uuid(); + responses.add(GraphOperationMessage::NewCustomLayer { + id, + nodes: entry.nodes, + parent, insert_index: -1, - duplicating: false, - }); - responses.add(DocumentMessage::UpdateLayerMetadata { - layer_path: destination_path, - layer_metadata: entry.layer_metadata, }); + if entry.selected { + responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); + } } responses.add(DocumentMessage::CommitTransaction); @@ -557,7 +544,6 @@ impl MessageHandler>, layer_path: Vec, responses: &mut VecDeque) { let id = *layer_path.last().unwrap(); responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths }); - responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![id] }) + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }) } /// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame`] @@ -232,7 +232,7 @@ impl<'a> NodeGraphLayer<'a> { /// Return an iterator up the primary flow of the layer pub fn primary_layer_flow(&self) -> impl Iterator { - self.node_graph.primary_flow_from_opt(Some(self.layer_node)) + self.node_graph.primary_flow_from_node(Some(self.layer_node)) } /// Does a node exist in the layer's primary flow diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 9670ee2b4d..d6eb0567d3 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -248,9 +248,9 @@ impl PathToolData { // We didn't find a point nearby, so consider selecting the nearest shape instead else if let Some(layer) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) { if shift { - responses.add(NodeGraphMessage::AddSelectNodes { nodes: vec![layer.to_node()] }); + responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] }); } else { - responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![layer.to_node()] }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); } self.drag_start_pos = input.mouse.position; self.previous_mouse_position = input.mouse.position; @@ -386,7 +386,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(NodeGraphMessage::SetSelectedNodes { nodes: vec![] }); + responses.add(NodeGraphMessage::SelectedNodesSet { 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 +401,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(NodeGraphMessage::SetSelectedNodes { nodes: vec![] }); + responses.add(NodeGraphMessage::SelectedNodesSet { 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/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 998b4e3c7e..00ff57c10e 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -369,7 +369,7 @@ impl SelectToolData { } // Select the originals - responses.add(NodeGraphMessage::SetSelectedNodes { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: originals.iter().map(|layer| layer.to_node()).collect::>(), }); @@ -714,13 +714,13 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging.clear(); tool_data.layers_dragging.extend(replacement_selected_layers.iter()); - responses.add(NodeGraphMessage::SetSelectedNodes { + responses.add(NodeGraphMessage::SelectedNodesSet { 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::SetSelectedNodes { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![selecting_layer.to_node()], }); } @@ -778,7 +778,7 @@ impl Fsm for SelectToolFsmState { 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.metadata().intersect_quad(quad, &document.document_legacy.document_network).collect(); - responses.add_front(NodeGraphMessage::SetSelectedNodes { + responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); responses.add_front(DocumentMessage::Overlays( @@ -906,7 +906,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque, selected: Vec let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata())); tool_data.layers_dragging = vec![new_selected]; - responses.add(NodeGraphMessage::SetSelectedNodes { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); // tool_data @@ -916,7 +916,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque, selected: Vec fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Vec, tool_data: &mut SelectToolData) { tool_data.layers_dragging.append(&mut selected); - responses.add(NodeGraphMessage::SetSelectedNodes { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); // tool_data @@ -937,7 +937,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: return; }; - responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![new_selected.to_node()] }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] }); } fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document: &Document, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 6cda137656..29ebea4245 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -269,7 +269,7 @@ impl TextToolData { self.set_editing(true, render_data, document, responses); - responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![self.layer.to_node()] }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); } fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) -> TextToolFsmState { @@ -304,7 +304,7 @@ impl TextToolData { self.set_editing(true, render_data, document, responses); - responses.add(NodeGraphMessage::SetSelectedNodes { nodes: self.layer.to_path() }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: self.layer.to_path() }); TextToolFsmState::Editing } else { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 1a0d3b0734..34eac327ea 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -279,17 +279,6 @@ impl NodeRuntime { let old_thumbnail = self.thumbnails.entry(node_id).or_default(); if *old_thumbnail != render.svg { - responses.add(FrontendMessage::UpdateDocumentLayerDetails { - data: LayerPanelEntry { - name: "Layer".to_string(), - tooltip: format!("Layer id: {node_id}"), - visible: true, - layer_type: LayerDataTypeDiscriminant::Layer, - layer_metadata: LayerMetadata::new(true), - path: vec![node_id], - thumbnail: render.svg.to_string(), - }, - }); responses.add(FrontendMessage::UpdateNodeThumbnail { id: node_id, value: render.svg.to_string(), @@ -516,6 +505,26 @@ impl NodeGraphExecutor { new_upstream_transforms, transform, }) => { + for (&node_id, svg) in &new_thumbnails { + if !document.document_network.nodes.contains_key(&node_id) { + warn!("Missing node"); + continue; + } + responses.add(FrontendMessage::UpdateDocumentLayerDetails { + data: LayerPanelEntry { + name: "Layer".to_string(), + tooltip: format!("Layer id: {node_id}"), + visible: true, + layer_type: LayerDataTypeDiscriminant::Layer, + layer_metadata: LayerMetadata { + expanded: true, + selected: document.metadata.selected_layers_contains(LayerNodeIdentifier::new(node_id, &document.document_network)), + }, + path: vec![node_id], + thumbnail: svg.to_string(), + }, + }); + } self.thumbnails = new_thumbnails; document.metadata.update_transforms(new_transforms, new_upstream_transforms); document.metadata.update_click_targets(new_click_targets); diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 5ed463f2d7..e6defe20b8 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -5,6 +5,7 @@ use crate::helpers::{translate_key, Error}; use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES}; +use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::LayerId; use editor::application::generate_uuid; use editor::application::Editor; @@ -546,11 +547,9 @@ impl JsEditorHandle { /// Move a layer to be next to the specified neighbor #[wasm_bindgen(js_name = moveLayerInTree)] pub fn move_layer_in_tree(&self, folder_path: Vec, insert_index: isize) { - let message = DocumentMessage::MoveSelectedLayersTo { - folder_path, - insert_index, - reverse_index: true, - }; + let parent = folder_path.last().copied().map(LayerNodeIdentifier::new_unchecked).unwrap_or(LayerNodeIdentifier::ROOT); + + let message = DocumentMessage::MoveSelectedLayersTo { parent, insert_index }; self.dispatch(message); } @@ -654,7 +653,7 @@ impl JsEditorHandle { #[wasm_bindgen(js_name = selectNodes)] pub fn select_nodes(&self, nodes: Option>) { let nodes = nodes.unwrap_or_default(); - let message = NodeGraphMessage::SetSelectedNodes { nodes }; + let message = NodeGraphMessage::SelectedNodesSet { nodes }; self.dispatch(message); } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 5a9b00cfdc..862f477f5b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -616,13 +616,23 @@ impl NodeNetwork { FlowIter { stack: self.outputs.iter().map(|output| output.node_id).collect(), network: self, + primary: true, } } - pub fn primary_flow_from_opt(&self, id: Option) -> impl Iterator { + pub fn primary_flow_from_node(&self, id: Option) -> impl Iterator { FlowIter { stack: id.map_or_else(|| self.outputs.iter().map(|output| output.node_id).collect(), |id| vec![id]), network: self, + primary: true, + } + } + + pub fn all_dependencies(&self, id: NodeId) -> impl Iterator { + FlowIter { + stack: vec![id], + network: self, + primary: false, } } @@ -664,6 +674,7 @@ impl NodeNetwork { struct FlowIter<'a> { stack: Vec, network: &'a NodeNetwork, + primary: bool, } impl<'a> Iterator for FlowIter<'a> { type Item = (&'a DocumentNode, NodeId); @@ -671,13 +682,11 @@ impl<'a> Iterator for FlowIter<'a> { loop { let node_id = self.stack.pop()?; if let Some(document_node) = self.network.nodes.get(&node_id) { - self.stack.extend( - document_node - .inputs - .iter() - .take(1) // Only show the primary input - .filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }), - ); + let inputs = document_node.inputs.iter().take(if self.primary { 1 } else { usize::MAX }); + let node_ids = inputs.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(*node_id) } else { None }); + + self.stack.extend(node_ids); + return Some((document_node, node_id)); }; }