From 4c3856833b473b05a56441a4ad2c26a1d7c62cdf Mon Sep 17 00:00:00 2001 From: adamgerhant <116332429+adamgerhant@users.noreply.github.com> Date: Sat, 4 May 2024 22:11:48 -0700 Subject: [PATCH] Code cleanup and refactor for generalized layers (#1738) * Move functions to messages to fix undo bugs for UnGroup and Group * Copy+Paste for generalized layer nodes * Fix MoveSelectedLayersTo and GroupSelectedLayers by extracting functions into messages * Fix tests, replace FrontendMessage:TriggerPaste with PortfolioMessage::PasteIntoFolder * Formatting --------- Co-authored-by: Keavon Chambers --- .../document/document_message_handler.rs | 375 +++++------------- .../graph_operation_message.rs | 30 +- .../graph_operation_message_handler.rs | 302 +++++++++++--- .../document/node_graph/node_graph_message.rs | 14 + .../node_graph/node_graph_message_handler.rs | 143 ++++--- .../portfolio/portfolio_message_handler.rs | 65 +-- .../messages/tool/tool_messages/brush_tool.rs | 2 +- .../tool/tool_messages/ellipse_tool.rs | 2 +- .../tool/tool_messages/freehand_tool.rs | 2 +- .../messages/tool/tool_messages/line_tool.rs | 2 +- .../messages/tool/tool_messages/pen_tool.rs | 2 +- .../tool/tool_messages/polygon_tool.rs | 2 +- .../tool/tool_messages/rectangle_tool.rs | 2 +- .../tool/tool_messages/spline_tool.rs | 2 +- .../messages/tool/tool_messages/text_tool.rs | 2 +- 15 files changed, 507 insertions(+), 440 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 16c263ed..a5db05a0 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1,3 +1,4 @@ +use super::utility_types::clipboards::Clipboard; use super::utility_types::error::EditorError; use super::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, OptionBoundsSnapping, OptionPointSnapping, SnappingOptions, SnappingState}; use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; @@ -5,9 +6,7 @@ use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH}; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; -use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type; use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData; use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options}; use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; @@ -279,36 +278,16 @@ impl MessageHandler> for DocumentMessag responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); } DocumentMessage::DuplicateSelectedLayers => { - self.backup(responses); - for layer_ancestors in self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)) { - let Some(layer) = layer_ancestors.last().copied() else { continue }; - let Some(parent) = layer.parent(&self.metadata) else { continue }; - let Some(node) = self.network().nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else { - continue; - }; + let parent = self.new_layer_parent(false); + let calculated_insert_index = self.get_calculated_insert_index(parent); - let nodes = NodeGraphMessageHandler::copy_nodes( - self.network(), - &self - .network() - .upstream_flow_back_from_nodes(vec![node], FlowType::UpstreamFlow) - .enumerate() - .map(|(index, (_, node_id))| (node_id, NodeId(index as u64))) - .collect(), - ) - .collect(); - - let id = NodeId(generate_uuid()); - let selected_layer_index = parent.children(self.metadata()).collect::>().iter().position(|&sibling| sibling == layer).unwrap_or(0); - let insert_index = if (selected_layer_index as i64 - 1) < 0 { -1 } else { selected_layer_index as isize }; - responses.add(GraphOperationMessage::NewCustomLayer { - id, - nodes, - parent, - insert_index, - alias: String::new(), - }); - } + responses.add(DocumentMessage::StartTransaction); + responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal }); + responses.add(PortfolioMessage::PasteIntoFolder { + clipboard: Clipboard::Internal, + parent, + insert_index: calculated_insert_index, + }); } DocumentMessage::FlipSelectedLayers { flip_axis } => { self.backup(responses); @@ -356,6 +335,8 @@ impl MessageHandler> for DocumentMessag responses.add(OverlaysMessage::Draw); } DocumentMessage::GroupSelectedLayers => { + responses.add(DocumentMessage::StartTransaction); + let parent = self .metadata() .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false) @@ -390,8 +371,10 @@ impl MessageHandler> for DocumentMessag continue; } - // Disconnect above and below the old layer location - self.disconnect_node(layer, responses); + responses.add(NodeGraphMessage::DisconnectLayerFromStack { + node_id: layer.to_node(), + reconnect_to_sibling: true, + }); // Move disconnected node to folder let folder_position = self @@ -401,118 +384,48 @@ impl MessageHandler> for DocumentMessag .expect("Current folder should always exist") .metadata .position; - - let Some(layer_to_move_node_mut) = self.network.nodes.get_mut(&layer.to_node()) else { - return; - }; - - DocumentMessageHandler::disconnect_input(layer_to_move_node_mut, 0); - layer_to_move_node_mut.metadata.position = folder_position; + responses.add(NodeGraphMessage::SetNodePosition { + node_id: layer.to_node(), + position: folder_position, + }); // Insert node right above the folder - // TODO: Use insert layer between message let Some((folder_downstream_node_id, folder_downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder) else { log::error!("Downstream node should always exist when inserting layer"); return; }; - let downstream_input = self - .network - .nodes - .get_mut(&folder_downstream_node_id) - .and_then(|node| node.inputs.get_mut(folder_downstream_input_index)); - let Some(NodeInput::Node { node_id, .. }) = downstream_input else { - log::error!("Downstream node should have a node input"); - return; - }; - *node_id = layer.to_node(); + responses.add(GraphOperationMessage::InsertNodeBetween { + post_node_id: folder_downstream_node_id, + post_node_input_index: folder_downstream_input_index, + insert_node_output_index: 0, + insert_node_id: layer.to_node(), + insert_node_input_index: 0, + pre_node_output_index: 0, + pre_node_id: first_unselected_parent_folder.to_node(), + }); - // Connect layer primary input to parent folder - let Some(layer_node_input) = self.network.nodes.get_mut(&layer.to_node()).and_then(|node| node.inputs.get_mut(0)) else { - log::error!("Layer should always have primary input"); - return; - }; - *layer_node_input = NodeInput::node(first_unselected_parent_folder.to_node(), 0); - - let upstream_shift = IVec2::new(0, 3); - let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - modify_inputs.shift_upstream(first_unselected_parent_folder.to_node(), upstream_shift, true); + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: first_unselected_parent_folder.to_node(), + shift: IVec2::new(0, 3), + shift_self: true, + }); } - let calculated_insert_index = parent.children(self.metadata()).enumerate().find_map(|(index, direct_child)| { - if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == direct_child) { - return Some(index as isize); - } - - for descendant in direct_child.descendants(self.metadata()) { - if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == descendant) { - return Some(index as isize); - } - } - - None - }); + let calculated_insert_index = self.get_calculated_insert_index(parent); let folder_id = NodeId(generate_uuid()); - responses.add(DocumentMessage::StartTransaction); responses.add(GraphOperationMessage::NewCustomLayer { id: folder_id, nodes: HashMap::new(), parent, - insert_index: calculated_insert_index.unwrap_or(-1), + insert_index: calculated_insert_index, alias: String::new(), }); - // Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer - let mut nodes_to_move = Vec::new(); + responses.add(GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent: folder_id }); - // Skip over horizontal non layer node chain that feeds into parent - let Some(mut current_stack_node_id) = parent.first_child(&self.metadata).and_then(|current_stack_node| Some(current_stack_node.to_node())) else { - log::error!("Folder should always have child"); - return; - }; - let current_stack_node_id = &mut current_stack_node_id; - - loop { - let mut current_stack_node = self.network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node"); - - // Check if the current stack node is a selected layer - if self - .selected_nodes - .selected_layers(&self.metadata) - .any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id) - { - nodes_to_move.push(*current_stack_node_id); - - // Push all non layer sibling nodes directly upstream of the selected layer - loop { - let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break }; - - let next_node = self.network.nodes.get(node_id).expect("Stack node id should always be a node"); - - // If the next node is a layer, immediately break and leave current stack node as the non layer node - if next_node.is_layer { - break; - } - - *current_stack_node_id = *node_id; - current_stack_node = next_node; - - nodes_to_move.push(*current_stack_node_id); - } - } - - // Get next node - let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break }; - *current_stack_node_id = *node_id; - } - - responses.add(GraphOperationMessage::MoveUpstreamSiblingsToChild { - new_parent: folder_id, - upstream_sibling_ids: nodes_to_move, - }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] }); - responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(NodeGraphMessage::SendGraph); @@ -576,74 +489,23 @@ impl MessageHandler> for DocumentMessag let binding = self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)); let get_last_elements = binding.iter().map(|x| x.last().expect("empty path")).collect::>(); - let mut run_document_graph_after = false; - // TODO: The `.collect()` is necessary to avoid borrowing issues with `self`. See if this can be avoided to improve performance. let ordered_last_elements = self.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).rev().collect::>(); for layer_to_move in ordered_last_elements { - if !run_document_graph_after && self.network.connected_to_output(layer_to_move.to_node()) { - run_document_graph_after = true; - } - - // Part 1: Disconnect layer to move and reconnect downstream node to upstream sibling if it exists. - self.disconnect_node(layer_to_move, responses); - - // Part 2: Reconnect layer_to_move to new parent at insert index. - let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&self.network, parent.to_node(), insert_index); - - // Layer_to_move should always correspond to a node. - let Some(layer_to_move_node) = self.network.nodes.get(&layer_to_move.to_node()) else { - continue; - }; - - // Move current layer to post node. - let post_node = self.network.nodes.get(&post_node_id).expect("Post node id should always refer to a node"); - let current_position = layer_to_move_node.metadata.position; - let new_position = post_node.metadata.position; - - // If moved to top of a layer stack, move to the left of the post node. The stack will be shifted down later. - // If moved within a stack, move directly on the post node. The rest of the stack will be shifted down later. - let offset_to_post_node = if insert_index == 0 { - new_position - current_position - IVec2::new(8, 0) - } else { - new_position - current_position - }; - - let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - modify_inputs.shift_upstream(layer_to_move.to_node(), offset_to_post_node, true); - - // Update post_node input to layer_to_move. - // TODO: Use insert layer between message - let post_node_mut = self.network.nodes.get_mut(&post_node_id).expect("Post node id should always refer to a node"); - if let Some(NodeInput::Node { node_id, .. }) = post_node_mut.inputs.get_mut(post_node_input_index) { - *node_id = layer_to_move.to_node(); - } else if let Some(node_input) = post_node_mut.inputs.get_mut(post_node_input_index) { - *node_input = NodeInput::node(layer_to_move.to_node(), 0); - } - - let Some(layer_to_move_node_mut) = self.network.nodes.get_mut(&layer_to_move.to_node()) else { - continue; - }; - - if let Some(pre_node_id) = pre_node_id { - // If pre node exists, connect layer_to_move sibling input to that node. - if let Some(node_input) = layer_to_move_node_mut.inputs.get_mut(0) { - *node_input = NodeInput::node(pre_node_id, 0); - } - } - - // shift stack down, starting at the moved node. - let mut modify_inputs: ModifyInputsContext = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - let shift = IVec2::new(0, 3); - modify_inputs.shift_upstream(layer_to_move.to_node(), shift, true); - - self.metadata.load_structure(&self.network, &mut self.selected_nodes); + // Disconnect layer to move and reconnect downstream node to upstream sibling if it exists. + responses.add(NodeGraphMessage::DisconnectLayerFromStack { + node_id: layer_to_move.to_node(), + reconnect_to_sibling: true, + }); + // Reconnect layer_to_move to new parent at insert index. + responses.add(GraphOperationMessage::InsertLayerAtStackIndex { + layer_id: layer_to_move.to_node(), + parent: parent.to_node(), + insert_index, + }); } - if run_document_graph_after { - responses.add(NodeGraphMessage::RunDocumentGraph); - } - responses.add(DocumentMessage::DocumentStructureChanged); + responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::SendGraph); } DocumentMessage::NudgeSelectedLayers { @@ -736,7 +598,7 @@ impl MessageHandler> for DocumentMessag let image_frame = ImageFrame { image, ..Default::default() }; use crate::messages::tool::common_functionality::graph_modification_utils; - let layer = graph_modification_utils::new_image_layer(image_frame, NodeId(generate_uuid()), self.new_layer_parent(), responses); + let layer = graph_modification_utils::new_image_layer(image_frame, NodeId(generate_uuid()), self.new_layer_parent(true), responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); @@ -754,7 +616,7 @@ impl MessageHandler> for DocumentMessag use crate::messages::tool::common_functionality::graph_modification_utils; let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into()); let center_in_viewport = DAffine2::from_translation(self.metadata().document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left)); - let layer = graph_modification_utils::new_svg_layer(svg, center_in_viewport, NodeId(generate_uuid()), self.new_layer_parent(), responses); + let layer = graph_modification_utils::new_svg_layer(svg, center_in_viewport, NodeId(generate_uuid()), self.new_layer_parent(true), responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select }); } @@ -994,14 +856,8 @@ impl MessageHandler> for DocumentMessag DocumentMessage::UngroupSelectedLayers => { responses.add(DocumentMessage::StartTransaction); - let mut run_document_graph_after = false; - let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata())); for folder in folder_paths { - if !run_document_graph_after && self.network.connected_to_output(folder.to_node()) { - run_document_graph_after = true; - } - // Cannot ungroup artboard let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist"); if folder_node.is_artboard() { @@ -1017,30 +873,29 @@ impl MessageHandler> for DocumentMessag // Move child_layer stack x position to folder stack let child_layer_node = self.network.nodes.get(&child_layer_node_id).expect("Child node should always exist for layer"); let offset = folder_node.metadata.position - child_layer_node.metadata.position; - let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - modify_inputs.shift_upstream(child_layer_node_id, offset, true); + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: child_layer_node_id, + shift: offset, + shift_self: true, + }); - // Set the input for the node downstream of folder to the first layer node + // Set the primary input for the node downstream of folder to the first layer node let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, folder) else { log::error!("Downstream node should always exist when moving layer"); continue; }; - let Some(NodeInput::Node { node_id, .. }) = self - .network - .nodes - .get_mut(&downstream_node_id) - .expect("downstream node should always exist") - .inputs - .get_mut(downstream_input_index) - else { - log::error!("Could not get downstream node input"); - continue; - }; - *node_id = child_layer_node_id; + + // Output_index must be 0 since layers only have 1 output + let downstream_input = NodeInput::node(child_layer_node_id, 0); + responses.add(NodeGraphMessage::SetNodeInput { + node_id: downstream_node_id, + input_index: downstream_input_index, + input: downstream_input, + }); // Get the node that feeds into the primary input for the folder (if it exists) if let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&folder.to_node()).expect("Folder should always exist").inputs.get(0) { - let upstream_sibling_id = *node_id; + let layer_upstream_sibling_id = *node_id; // Get the node at the bottom of the first layer node stack let mut last_child_node_id = child_layer_node_id; @@ -1051,12 +906,13 @@ impl MessageHandler> for DocumentMessag last_child_node_id = *node_id; } - // Connect the primary input of the bottom layer to the node to the upstream sibling - let Some(node_input) = self.network.nodes.get_mut(&last_child_node_id).expect("Last child node should always exist").inputs.get_mut(0) else { - log::error!("Could not get last child node primary input"); - continue; - }; - *node_input = NodeInput::node(upstream_sibling_id, 0); + // Connect the primary input of the bottom layer of the node to the upstream sibling + let bottom_layer_node_input = NodeInput::node(layer_upstream_sibling_id, 0); + responses.add(NodeGraphMessage::SetNodeInput { + node_id: last_child_node_id, + input_index: 0, + input: bottom_layer_node_input, + }); // Shift upstream_sibling down by the height of the child layer stack let top_of_stack = self.network.nodes.get(&child_layer_node_id).expect("Child layer should always exist for child layer id"); @@ -1064,24 +920,25 @@ impl MessageHandler> for DocumentMessag let target_distance = bottom_of_stack.metadata.position.y - top_of_stack.metadata.position.y; let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist"); - let upstream_sibling_node = self.network.nodes.get(&upstream_sibling_id).expect("Upstream sibling node should always exist"); + let upstream_sibling_node = self.network.nodes.get(&layer_upstream_sibling_id).expect("Upstream sibling node should always exist"); let current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y; let y_offset = target_distance - current_distance + 3; - let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - modify_inputs.shift_upstream(upstream_sibling_id, IVec2::new(0, y_offset), true); + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: layer_upstream_sibling_id, + shift: IVec2::new(0, y_offset), + shift_self: true, + }); } - // Delete folder and all horizontal inputs + // Delete folder and all horizontal inputs, also deletes node in metadata responses.add(NodeGraphMessage::DeleteNodes { node_ids: vec![folder.to_node()], reconnect: true, }); } - if run_document_graph_after { - responses.add(NodeGraphMessage::RunDocumentGraph); - } + responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(NodeGraphMessage::SendGraph); } @@ -1385,23 +1242,6 @@ impl DocumentMessageHandler { self.saved_hash = None; } } - // TODO: Replace with disconnect message - pub fn disconnect_input(layer_to_disconnect_node: &mut DocumentNode, input_index: usize) { - let Some(node_type) = resolve_document_node_type(&layer_to_disconnect_node.name) else { - warn!("Node {} not in library", layer_to_disconnect_node.name); - return; - }; - let Some(existing_input) = layer_to_disconnect_node.inputs.get_mut(input_index) else { - warn!("Node does not have and input at the selected index"); - return; - }; - - let mut default_input = node_type.inputs[input_index].default.clone(); - if let NodeInput::Value { exposed, .. } = &mut default_input { - *exposed = existing_input.is_exposed(); - } - *existing_input = default_input; - } pub fn get_downstream_node(network: &NodeNetwork, metadata: &DocumentMetadata, layer_to_move: LayerNodeIdentifier) -> Option<(NodeId, usize)> { let mut downstream_layer = None; @@ -1442,35 +1282,6 @@ impl DocumentMessageHandler { }) } - // TODO: move into message - pub fn disconnect_node(&mut self, layer_to_disconnect: LayerNodeIdentifier, responses: &mut VecDeque) { - let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, layer_to_disconnect) else { - log::error!("Downstream node should always exist when moving layer"); - return; - }; - - let layer_to_move_sibling_input = self.network.nodes.get(&layer_to_disconnect.to_node()).and_then(|node| node.inputs.get(0)); - if let Some(NodeInput::Node { node_id, .. }) = layer_to_move_sibling_input { - let upstream_sibling_id = node_id.clone(); - let Some(downstream_node) = self.network.nodes.get_mut(&downstream_node_id) else { return }; - - if let Some(NodeInput::Node { node_id, .. }) = downstream_node.inputs.get_mut(downstream_input_index) { - *node_id = upstream_sibling_id; - } - - let upstream_shift = IVec2::new(0, -3); - let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); - - modify_inputs.shift_upstream(upstream_sibling_id, upstream_shift, true); - } else { - // Disconnect node directly downstream if upstream sibling doesn't exist - let Some(downstream_node) = self.network.nodes.get_mut(&downstream_node_id) else { return }; - DocumentMessageHandler::disconnect_input(downstream_node, downstream_input_index); - } - - let Some(to_move) = self.network.nodes.get_mut(&layer_to_disconnect.to_node()) else { return }; - DocumentMessageHandler::disconnect_input(to_move, 0); - } /// 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. @@ -1481,12 +1292,32 @@ impl DocumentMessageHandler { } /// Finds the parent folder which, based on the current selections, should be the container of any newly added layers. - pub fn new_layer_parent(&self) -> LayerNodeIdentifier { + pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier { self.metadata() - .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true) + .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), include_self) .unwrap_or_else(|| self.metadata().active_artboard()) } + fn get_calculated_insert_index(&self, parent: LayerNodeIdentifier) -> isize { + parent + .children(self.metadata()) + .enumerate() + .find_map(|(index, direct_child)| { + if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == direct_child) { + return Some(index as isize); + } + + for descendant in direct_child.descendants(self.metadata()) { + if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == descendant) { + return Some(index as isize); + } + } + + None + }) + .unwrap_or(-1) + } + /// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document. pub fn load_layer_resources(&self, responses: &mut VecDeque) { let mut fonts = HashSet::new(); diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index 22313822..18048266 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -19,10 +19,36 @@ use glam::{DAffine2, DVec2, IVec2}; #[impl_message(Message, DocumentMessage, GraphOperation)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum GraphOperationMessage { + AddNodesAsChild { + nodes: HashMap, + parent: LayerNodeIdentifier, + insert_index: isize, + }, + DisconnectInput { + node_id: NodeId, + input_index: usize, + }, FillSet { layer: LayerNodeIdentifier, fill: Fill, }, + InsertLayerAtStackIndex { + layer_id: NodeId, + parent: NodeId, + insert_index: usize, + }, + InsertNodeBetween { + post_node_id: NodeId, + post_node_input_index: usize, + insert_node_output_index: usize, + insert_node_id: NodeId, + insert_node_input_index: usize, + pre_node_output_index: usize, + pre_node_id: NodeId, + }, + MoveSelectedSiblingsToChild { + new_parent: NodeId, + }, OpacitySet { layer: LayerNodeIdentifier, opacity: f64, @@ -64,10 +90,6 @@ pub enum GraphOperationMessage { layer: LayerNodeIdentifier, strokes: Vec, }, - MoveUpstreamSiblingsToChild { - new_parent: NodeId, - upstream_sibling_ids: Vec, - }, NewArtboard { id: NodeId, artboard: Artboard, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 931a5dba..d0bfd2c8 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -1,5 +1,6 @@ use super::transform_utils::{self, LayerBounds}; use super::utility_types::ModifyInputsContext; +use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::messages::prelude::*; @@ -36,11 +37,185 @@ impl MessageHandler> for Gr } = data; match message { + GraphOperationMessage::AddNodesAsChild { nodes, parent, insert_index } => { + let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect(); + + let shift = nodes + .get(&NodeId(0)) + .and_then(|node| { + document_network + .nodes + .get(&parent.to_node()) + .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 + document_network.nodes.insert(node_id, document_node); + } + + let Some(new_layer_id) = new_ids.get(&NodeId(0)) else { + log::error!("Could not get layer node when adding as child"); + return; + }; + + let insert_index = if insert_index < 0 { 0 } else { insert_index as usize }; + let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent.to_node(), insert_index); + + responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] }); + + if let Some(upstream_node) = upstream_node { + responses.add(GraphOperationMessage::InsertNodeBetween { + post_node_id: downstream_node, + post_node_input_index: input_index, + insert_node_output_index: 0, + insert_node_id: *new_layer_id, + insert_node_input_index: 0, + pre_node_output_index: 0, + pre_node_id: upstream_node, + }) + } else { + responses.add(NodeGraphMessage::SetNodeInput { + node_id: downstream_node, + input_index: input_index, + input: NodeInput::node(*new_layer_id, 0), + }) + } + + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: *new_layer_id, + shift: IVec2::new(0, 3), + shift_self: true, + }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + GraphOperationMessage::DisconnectInput { node_id, input_index } => { + let Some(node_to_disconnect) = document_network.nodes.get(&node_id) else { + warn!("Node {} not found in DisconnectInput", node_id); + return; + }; + let Some(node_type) = resolve_document_node_type(&node_to_disconnect.name) else { + warn!("Node {} not in library", node_to_disconnect.name); + return; + }; + let Some(existing_input) = node_to_disconnect.inputs.get(input_index) else { + warn!("Node does not have an input at the selected index"); + return; + }; + + let mut input = node_type.inputs[input_index].default.clone(); + if let NodeInput::Value { exposed, .. } = &mut input { + *exposed = existing_input.is_exposed(); + } + responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); + } GraphOperationMessage::FillSet { layer, fill } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) { modify_inputs.fill_set(fill); } } + GraphOperationMessage::InsertLayerAtStackIndex { layer_id, parent, insert_index } => { + let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&document_network, parent, insert_index); + + // `layer_to_move` should always correspond to a node. + let Some(layer_to_move_node) = document_network.nodes.get(&layer_id) else { + log::error!("Layer node not found when inserting node {} at index {}", layer_id, insert_index); + return; + }; + + // Move current layer to post node. + let post_node = document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node"); + let current_position = layer_to_move_node.metadata.position; + let new_position = post_node.metadata.position; + + // If moved to top of a layer stack, move to the left of the post node. If moved within a stack, move directly on the post node. The stack will be shifted down later. + let offset_to_post_node = if insert_index == 0 { + new_position - current_position - IVec2::new(8, 0) + } else { + new_position - current_position + }; + + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: layer_id, + shift: offset_to_post_node, + shift_self: true, + }); + + // Update post_node input to layer_to_move. + if let Some(upstream_node) = pre_node_id { + responses.add(GraphOperationMessage::InsertNodeBetween { + post_node_id: post_node_id, + post_node_input_index: post_node_input_index, + insert_node_output_index: 0, + insert_node_id: layer_id, + insert_node_input_index: 0, + pre_node_output_index: 0, + pre_node_id: upstream_node, + }) + } else { + responses.add(NodeGraphMessage::SetNodeInput { + node_id: post_node_id, + input_index: post_node_input_index, + input: NodeInput::node(layer_id, 0), + }) + } + + // Shift stack down, starting at the moved node. + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: layer_id, + shift: IVec2::new(0, 3), + shift_self: true, + }); + } + GraphOperationMessage::InsertNodeBetween { + post_node_id, + post_node_input_index, + insert_node_output_index, + insert_node_id, + insert_node_input_index, + pre_node_output_index, + pre_node_id, + } => { + let Some(post_node) = document_network.nodes.get(&post_node_id) else { + error!("Post node not found"); + return; + }; + let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else { + error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}"); + return; + }; + let Some(insert_node) = document_network.nodes.get(&insert_node_id) else { + error!("Insert node not found"); + return; + }; + let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else { + error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}"); + return; + }; + + let post_input = NodeInput::node(insert_node_id, insert_node_output_index); + responses.add(NodeGraphMessage::SetNodeInput { + node_id: post_node_id, + input_index: post_node_input_index, + input: post_input, + }); + + let insert_input = NodeInput::node(pre_node_id, pre_node_output_index); + responses.add(NodeGraphMessage::SetNodeInput { + node_id: insert_node_id, + input_index: insert_node_input_index, + input: insert_input, + }); + } GraphOperationMessage::OpacitySet { layer, opacity } => { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) { modify_inputs.opacity_set(opacity); @@ -103,67 +278,78 @@ impl MessageHandler> for Gr modify_inputs.brush_modify(strokes); } } - GraphOperationMessage::MoveUpstreamSiblingsToChild { new_parent, upstream_sibling_ids } => { - // Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec - for node_to_move in upstream_sibling_ids.iter().rev() { - // Connect pre node to post node, or disconnect pre node if post node doesn't exist - let mut pre_node_id = new_parent; - loop { - let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get(&pre_node_id).and_then(|node| node.inputs.get(0)) else { - log::error!("End of stack should never be reached"); - return; - }; - if *node_id == *node_to_move { - break; - } - pre_node_id = *node_id; - } - - if let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get(&node_to_move).and_then(|node| node.inputs.get(0)) { - let post_node_id = *node_id; - let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get_mut(&pre_node_id).and_then(|node| node.inputs.get_mut(0)) else { - log::error!("Pre node should always have primary input"); - return; - }; - *node_id = post_node_id; - } else { - DocumentMessageHandler::disconnect_input(document_network.nodes.get_mut(&pre_node_id).expect("Upstream sibling should always exist"), 0); - } - - // Connect upstream sibling to the secondary input of the parent - let Some(parent_secondary_input) = document_network.nodes.get(&new_parent).and_then(|node| node.inputs.get(1)) else { - log::error!("Could not get child node input for current node"); - return; - }; - - // Insert upstream_sibling_node at top of group stack - if let NodeInput::Node { node_id, .. } = parent_secondary_input { - // If there is already a node at the top of the stack, insert upstream_sibling_node in between - let current_child = *node_id; - let Some(upstream_sibling_input) = document_network.nodes.get_mut(&node_to_move).and_then(|node| node.inputs.get_mut(0)) else { - log::error!("Could not get upstream sibling node input"); - return; - }; - *upstream_sibling_input = NodeInput::node(current_child, 0); - } - - let Some(parent_secondary_input_mut) = document_network.nodes.get_mut(&new_parent).and_then(|node| node.inputs.get_mut(1)) else { - log::error!("Could not get child node input for current node"); - return; - }; - - *parent_secondary_input_mut = NodeInput::node(*node_to_move, 0); - } - - let Some(most_upstream_sibling) = upstream_sibling_ids.last() else { + GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => { + let group_layer = LayerNodeIdentifier::new(new_parent, &document_network); + let Some(group_parent) = group_layer.parent(&document_metadata) else { + log::error!("Could not find parent for layer {:?}", group_layer); return; }; - DocumentMessageHandler::disconnect_input(document_network.nodes.get_mut(&most_upstream_sibling).expect("Upstream sibling should always exist"), 0); - let top_of_stack = upstream_sibling_ids.first().expect("Upstream nodes to move cannot be empty"); - let upstream_shift = IVec2::new(-8, 0); - let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); - modify_inputs.shift_upstream(*top_of_stack, upstream_shift, true); + // Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer + let mut selected_siblings = Vec::new(); + + // Skip over horizontal non layer node chain that feeds into parent + let Some(mut current_stack_node_id) = group_parent.first_child(&document_metadata).and_then(|current_stack_node| Some(current_stack_node.to_node())) else { + log::error!("Folder should always have child"); + return; + }; + let current_stack_node_id = &mut current_stack_node_id; + + loop { + let mut current_stack_node = document_network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node"); + + // Check if the current stack node is a selected layer + if selected_nodes + .selected_layers(&document_metadata) + .any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id) + { + selected_siblings.push(*current_stack_node_id); + + // Push all non layer sibling nodes directly upstream of the selected layer + loop { + let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break }; + + let next_node = document_network.nodes.get(node_id).expect("Stack node id should always be a node"); + + // If the next node is a layer, immediately break and leave current stack node as the non layer node + if next_node.is_layer { + break; + } + + *current_stack_node_id = *node_id; + current_stack_node = next_node; + + selected_siblings.push(*current_stack_node_id); + } + } + + // Get next node + let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break }; + *current_stack_node_id = *node_id; + } + + // Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec + for node_to_move in selected_siblings.iter().rev() { + // Connect downstream node to upstream node, or disconnect downstream node if upstream node doesn't exist + responses.add(NodeGraphMessage::DisconnectLayerFromStack { + node_id: *node_to_move, + reconnect_to_sibling: true, + }); + + responses.add(GraphOperationMessage::InsertLayerAtStackIndex { + layer_id: *node_to_move, + parent: new_parent, + insert_index: 0, + }); + } + + let Some(most_upstream_sibling) = selected_siblings.last() else { + return; + }; + responses.add(GraphOperationMessage::DisconnectInput { + node_id: *most_upstream_sibling, + input_index: 0, + }); } GraphOperationMessage::NewArtboard { id, artboard } => { let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); 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 6e7b6519..d19c7b75 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 @@ -1,5 +1,6 @@ use crate::messages::prelude::*; +use glam::IVec2; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; @@ -36,6 +37,10 @@ pub enum NodeGraphMessage { node_id: NodeId, input_index: usize, }, + DisconnectLayerFromStack { + node_id: NodeId, + reconnect_to_sibling: bool, + }, EnterNestedNetwork { node: NodeId, }, @@ -92,6 +97,10 @@ pub enum NodeGraphMessage { input_index: usize, input: NodeInput, }, + SetNodePosition { + node_id: NodeId, + position: IVec2, + }, SetQualifiedInputValue { node_path: Vec, input_index: usize, @@ -101,6 +110,11 @@ pub enum NodeGraphMessage { ShiftNode { node_id: NodeId, }, + ShiftUpstream { + node_id: NodeId, + shift: IVec2, + shift_self: bool, + }, SetVisibility { node_id: NodeId, visible: bool, 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 bdbb7889..3ada209d 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 @@ -9,6 +9,7 @@ use crate::application::generate_uuid; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::graph_operation::load_network_structure; +use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_types::{resolve_document_node_type, DocumentInputType, NodePropertiesContext}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes}; @@ -161,15 +162,11 @@ impl<'a> MessageHandler> for NodeGrap let outward_links = document_network.collect_outwards_links(); for (_, upstream_id) in document_network.upstream_flow_back_from_nodes(vec![*child_id], graph_craft::document::FlowType::UpstreamFlow) { - // TODO: move into a document_network function .is_sole_dependent. This function does a downstream traversal starting from the current node, - // TODO: and only traverses for nodes that are not in the delete_nodes set. If all downstream nodes converge to some node in the delete_nodes set, - // TODO: then it is a sole dependent. If the output node is eventually reached, then it is not a sole dependent. This means disconnected branches - // TODO: that do not feed into the delete_nodes set or the output node will be deleted. - + // This does a downstream traversal starting from the current node, and ending at either a node in the delete_nodes set or the output. + // If the traversal find as child node of a node in the delete_nodes set, then it is a sole dependent. If the output node is eventually reached, then it is not a sole dependent. let mut stack = vec![upstream_id]; let mut can_delete = true; - // TODO: Add iteration limit to force break in case of infinite while loop while let Some(current_node) = stack.pop() { if let Some(downstream_nodes) = outward_links.get(¤t_node) { for downstream_node in downstream_nodes { @@ -238,35 +235,65 @@ impl<'a> MessageHandler> for NodeGrap }); } NodeGraphMessage::DisconnectNodes { node_id, input_index } => { + responses.add(DocumentMessage::StartTransaction); + responses.add(GraphOperationMessage::DisconnectInput { node_id, input_index }); + let Some(network) = document_network.nested_network(&self.network) else { warn!("No network"); return; }; - let Some(node) = network.nodes.get(&node_id) else { - warn!("Invalid node"); - return; - }; - let Some(node_type) = resolve_document_node_type(&node.name) else { - warn!("Node {} not in library", node.name); - return; - }; - let Some((input_index, existing_input)) = node.inputs.iter().enumerate().filter(|(_, input)| input.is_exposed()).nth(input_index) else { - return; - }; - - let mut input = node_type.inputs[input_index].default.clone(); - if let NodeInput::Value { exposed, .. } = &mut input { - *exposed = existing_input.is_exposed(); - } - - responses.add(DocumentMessage::StartTransaction); - responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); if network.connected_to_output(node_id) { responses.add(NodeGraphMessage::RunDocumentGraph); } responses.add(NodeGraphMessage::SendGraph); } + NodeGraphMessage::DisconnectLayerFromStack { node_id, reconnect_to_sibling } => { + let Some(network) = document_network.nested_network(&self.network) else { + warn!("No network"); + return; + }; + + // Ensure node is a layer and create LayerNodeIdentifier + if network.nodes.get(&node_id).is_some_and(|node| !node.is_layer) { + log::error!("Non layer node passed to DisconnectLayer"); + return; + } + + let layer_to_disconnect = LayerNodeIdentifier::new(node_id, &network); + + let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&network, &document_metadata, layer_to_disconnect) else { + log::error!("Downstream node should always exist when moving layer"); + return; + }; + let layer_to_move_sibling_input = network.nodes.get(&layer_to_disconnect.to_node()).and_then(|node| node.inputs.get(0)); + if let Some(NodeInput::Node { node_id, .. }) = layer_to_move_sibling_input.and_then(|node_input| if reconnect_to_sibling { Some(node_input) } else { None }) { + let upstream_sibling_id = *node_id; + let Some(downstream_node) = document_network.nodes.get_mut(&downstream_node_id) else { return }; + + if let Some(NodeInput::Node { node_id, .. }) = downstream_node.inputs.get_mut(downstream_input_index) { + *node_id = upstream_sibling_id; + } + + let upstream_shift = IVec2::new(0, -3); + responses.add(NodeGraphMessage::ShiftUpstream { + node_id: upstream_sibling_id, + shift: upstream_shift, + shift_self: true, + }); + } else { + // Disconnect node directly downstream if upstream sibling doesn't exist + responses.add(GraphOperationMessage::DisconnectInput { + node_id: downstream_node_id, + input_index: downstream_input_index, + }); + } + + responses.add(GraphOperationMessage::DisconnectInput { + node_id: layer_to_disconnect.to_node(), + input_index: 0, + }); + } NodeGraphMessage::EnterNestedNetwork { node } => { if let Some(network) = document_network.nested_network(&self.network) { if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() { @@ -370,37 +397,16 @@ impl<'a> MessageHandler> for NodeGrap error!("No network"); return; }; - let Some(post_node) = network.nodes.get(&post_node_id) else { - error!("Post node not found"); - return; - }; - let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else { - error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}"); - return; - }; - let Some(insert_node) = network.nodes.get(&insert_node_id) else { - error!("Insert node not found"); - return; - }; - let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else { - error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}"); - return; - }; - responses.add(DocumentMessage::StartTransaction); - let post_input = NodeInput::node(insert_node_id, insert_node_output_index); - responses.add(NodeGraphMessage::SetNodeInput { - node_id: post_node_id, - input_index: post_node_input_index, - input: post_input, - }); - - let insert_input = NodeInput::node(pre_node_id, pre_node_output_index); - responses.add(NodeGraphMessage::SetNodeInput { - node_id: insert_node_id, - input_index: insert_node_input_index, - input: insert_input, + responses.add(GraphOperationMessage::InsertNodeBetween { + post_node_id, + post_node_input_index, + insert_node_output_index, + insert_node_id, + insert_node_input_index, + pre_node_output_index, + pre_node_id, }); if network.connected_to_output(insert_node_id) { @@ -520,6 +526,26 @@ impl<'a> MessageHandler> for NodeGrap } } } + NodeGraphMessage::SetNodePosition { node_id, position } => { + let Some(network) = document_network.nested_network_mut(&self.network) else { + warn!("No network"); + return; + }; + + let Some(node) = network.nodes.get_mut(&node_id) else { + log::error!("Failed to find node {node_id} when setting position"); + return; + }; + + node.metadata.position = position; + + // Since document structure doesn't change, just update the nodes + if graph_view_overlay_open { + let links = Self::collect_links(network); + let nodes = self.collect_nodes(&links, network); + responses.add(FrontendMessage::UpdateNodeGraph { nodes, links }); + } + } NodeGraphMessage::SetQualifiedInputValue { node_path, input_index, value } => { let Some((node_id, node_path)) = node_path.split_last() else { error!("Node path is empty"); @@ -591,6 +617,15 @@ impl<'a> MessageHandler> for NodeGrap self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses); } + NodeGraphMessage::ShiftUpstream { node_id, shift, shift_self } => { + let Some(network) = document_network.nested_network_mut(&self.network) else { + warn!("No network"); + return; + }; + + let mut modify_inputs = ModifyInputsContext::new(network, document_metadata, self, responses); + modify_inputs.shift_upstream(node_id, shift, shift_self); + } NodeGraphMessage::ToggleSelectedVisibility => { responses.add(DocumentMessage::StartTransaction); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 4b574946..6fbc4517 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -183,30 +183,29 @@ impl MessageHandler> for PortfolioMes let ordered_last_elements: Vec<_> = active_document.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).collect(); for layer in ordered_last_elements { - let node = layer.to_node(); - let previous_alias = active_document.network().nodes.get(&node).map(|node| node.alias.clone()).unwrap_or_default(); + let layer_node_id = layer.to_node(); + let previous_alias = active_document.network().nodes.get(&layer_node_id).map(|node| node.alias.clone()).unwrap_or_default(); - let Some(node) = active_document + let mut copy_ids = HashMap::new(); + copy_ids.insert(layer_node_id, NodeId(0 as u64)); + if let Some(input_node) = active_document .network() .nodes - .get(&node) + .get(&layer_node_id) .and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.get(0) }) .and_then(|input| input.as_node()) - else { - continue; + { + active_document + .network() + .upstream_flow_back_from_nodes(vec![input_node], graph_craft::document::FlowType::UpstreamFlow) + .enumerate() + .for_each(|(index, (_, node_id))| { + copy_ids.insert(node_id, NodeId((index + 1) as u64)); + }); }; buffer.push(CopyBufferEntry { - nodes: NodeGraphMessageHandler::copy_nodes( - active_document.network(), - &active_document - .network() - .upstream_flow_back_from_nodes(vec![node], graph_craft::document::FlowType::UpstreamFlow) - .enumerate() - .map(|(index, (_, node_id))| (node_id, NodeId(index as u64))) - .collect(), - ) - .collect(), + nodes: NodeGraphMessageHandler::copy_nodes(active_document.network(), ©_ids).collect(), selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()), visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()), locked: active_document.selected_nodes.layer_locked(layer, active_document.metadata()), @@ -384,26 +383,17 @@ impl MessageHandler> for PortfolioMes let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| { if self.active_document().is_some() { trace!("Pasting into folder {parent:?} as index: {insert_index}"); - let id = NodeId(generate_uuid()); - responses.add(GraphOperationMessage::NewCustomLayer { - id, - nodes: entry.nodes.clone(), + + responses.add(GraphOperationMessage::AddNodesAsChild { + nodes: entry.clone().nodes, parent, insert_index, - alias: entry.alias.clone(), }); - if entry.selected { - responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); - } - if !entry.visible { - responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false }); - } - if entry.locked { - responses.add(NodeGraphMessage::SetLocked { node_id: id, locked: true }); - } } }; + responses.add(DocumentMessage::DeselectAllLayers); + for entry in self.copy_buffer[clipboard as usize].iter().rev() { paste(entry, responses) } @@ -411,30 +401,19 @@ impl MessageHandler> for PortfolioMes PortfolioMessage::PasteSerializedData { data } => { if let Some(document) = self.active_document() { if let Ok(data) = serde_json::from_str::>(&data) { - let parent = document.new_layer_parent(); + let parent = document.new_layer_parent(false); responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::StartTransaction); for entry in data.into_iter().rev() { document.load_layer_resources(responses); - let id = NodeId(generate_uuid()); - responses.add(GraphOperationMessage::NewCustomLayer { - id, + responses.add(GraphOperationMessage::AddNodesAsChild { nodes: entry.nodes, parent, insert_index: -1, - alias: entry.alias, }); - if entry.selected { - responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); - } - if !entry.visible { - responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false }); - } } - - responses.add(DocumentMessage::CommitTransaction); } } } diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 4f9da195..07d24a83 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -420,7 +420,7 @@ fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque, ) { - let parent = document.new_layer_parent(); + let parent = document.new_layer_parent(true); // Deselect layers because we are now creating a new layer responses.add(DocumentMessage::DeselectAllLayers); diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index f39dae0d..34a87bdd 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -247,7 +247,7 @@ impl Fsm for PolygonToolFsmState { PolygonType::Convex => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.), PolygonType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), }; - let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); + let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses); polygon_data.layer = Some(layer); let fill_color = tool_options.fill.active_color(); diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 777be954..c5887195 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -209,7 +209,7 @@ impl Fsm for RectangleToolFsmState { responses.add(DocumentMessage::StartTransaction); - let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); + let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses); shape_data.layer = Some(layer); let fill_color = tool_options.fill.active_color(); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 2b5f3970..237cac5d 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -208,7 +208,7 @@ impl Fsm for SplineToolFsmState { responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::DeselectAllLayers); - let parent = document.new_layer_parent(); + let parent = document.new_layer_parent(true); let transform = document.metadata().transform_to_viewport(parent); //tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 6f1c0c1c..797d64c6 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -283,7 +283,7 @@ impl TextToolData { text: String::new(), font: editing_text.font.clone(), size: editing_text.font_size, - parent: document.new_layer_parent(), + parent: document.new_layer_parent(true), insert_index: -1, }); responses.add(GraphOperationMessage::FillSet {