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 410b8cfb72..1fcc35f583 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 @@ -13,7 +13,7 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::portfolio::document::utility_types::network_interface::{ - self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, + self, FlowType, InputConnector, LayerPosition, NodeNetworkInterface, NodePosition, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; @@ -763,7 +763,7 @@ impl<'a> MessageHandler> for NodeG network_interface.set_chain_position(&node_id, selection_network_path); } NodeGraphMessage::PasteNodes { serialized_nodes } => { - let data = match serde_json::from_str::>(&serialized_nodes) { + let mut data = match serde_json::from_str::>(&serialized_nodes) { Ok(d) => d, Err(e) => { warn!("Invalid node data {e:?}"); @@ -774,6 +774,48 @@ impl<'a> MessageHandler> for NodeG return; } + // Get network path of node overlay + let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { + log::error!("Could not get network metadata in PasteNodes"); + return; + }; + + let cursor_viewport_location = ipp.mouse.position; + let cursor_to_node_graph = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(cursor_viewport_location); + + // Sort the selected nodes by the new id so that we know which node was selected first + data.sort_by_key(|a| a.0); + + // Get position of the first node selected for copying that has an absolute position. Calculate paste offset from the cursor + // If no nodes with absolute position, then there is no offset from cursor + let copy_position_opt = data.iter().find_map(|(_, template)| match &template.persistent_node_metadata.node_type_metadata { + NodeTypePersistentMetadata::Layer(layer_metadata) => { + if let LayerPosition::Absolute(position) = &layer_metadata.position { + Some(position) + } else { + None + } + } + NodeTypePersistentMetadata::Node(node_metadata) => { + if let NodePosition::Absolute(position) = node_metadata.position() { + Some(position) + } else { + None + } + } + }); + + let copy_position = copy_position_opt.copied().unwrap_or_default(); + let graph_delta = IVec2::new( + ((cursor_to_node_graph.x / GRID_SIZE as f64).round()) as i32 - copy_position.x, + ((cursor_to_node_graph.y / GRID_SIZE as f64).round()) as i32 - copy_position.y, + ); + responses.add(DocumentMessage::AddTransaction); let new_ids: HashMap<_, _> = data.iter().map(|(id, _)| (*id, NodeId::new())).collect(); @@ -782,7 +824,10 @@ impl<'a> MessageHandler> for NodeG nodes: data, new_ids: new_ids.clone(), }); - responses.add(NodeGraphMessage::SelectedNodesSet { nodes }) + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + + // Shift nodes based on offset + responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true }) } NodeGraphMessage::PointerDown { shift_click, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index cdd8ef06a7..8b0bbe6d99 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -6712,6 +6712,10 @@ impl NodePersistentMetadata { pub fn new(position: NodePosition) -> Self { Self { position } } + + pub fn position(&self) -> &NodePosition { + &self.position + } } /// A layer can either be position as Absolute or in a Stack