Repository: GraphiteEditor/Graphite (language: rust)

Issue:
Make the Pen tool connect separate layers by their endpoints to become one layer



Discussion / hints:

Fix patch:
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 2dff2fbecf..9fa5becf87 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,7 +1,7 @@
 use super::transform_utils;
 use super::utility_types::ModifyInputsContext;
 use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
-use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
+use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};
 use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
 use crate::messages::prelude::*;
 
@@ -95,14 +95,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
 				}
 			}
 			GraphOperationMessage::SetUpstreamToChain { layer } => {
-				let Some(first_chain_node) = network_interface
-					.upstream_flow_back_from_nodes(
-						vec![layer.to_node()],
-						&[],
-						crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
-					)
-					.nth(1)
-				else {
+				let Some(OutputConnector::Node { node_id: first_chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[]) else {
 					return;
 				};
 
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 b2f12435dc..7f10967348 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
@@ -37,6 +37,10 @@ pub enum NodeGraphMessage {
 		output_connector: OutputConnector,
 		input_connector: InputConnector,
 	},
+	ConnectUpstreamOutputToInput {
+		downstream_input: InputConnector,
+		input_connector: InputConnector,
+	},
 	Cut,
 	DeleteNodes {
 		node_ids: Vec<NodeId>,
@@ -70,6 +74,10 @@ pub enum NodeGraphMessage {
 		parent: LayerNodeIdentifier,
 		insert_index: usize,
 	},
+	MoveNodeToChainStart {
+		node_id: NodeId,
+		parent: LayerNodeIdentifier,
+	},
 	PasteNodes {
 		serialized_nodes: String,
 	},
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 80ba4513ca..9ef034a285 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
@@ -210,6 +210,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
 				});
 				responses.add(NodeGraphMessage::SendGraph);
 			}
+			NodeGraphMessage::ConnectUpstreamOutputToInput { downstream_input, input_connector } => {
+				let Some(upstream_node) = network_interface.upstream_output_connector(&downstream_input, selection_network_path) else {
+					log::error!("Failed to find upstream node for downstream_input");
+					return;
+				};
+				responses.add(NodeGraphMessage::CreateWire {
+					output_connector: upstream_node,
+					input_connector,
+				});
+			}
 			NodeGraphMessage::Cut => {
 				responses.add(NodeGraphMessage::Copy);
 				responses.add(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
@@ -323,6 +333,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
 			NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index } => {
 				network_interface.move_layer_to_stack(layer, parent, insert_index, selection_network_path);
 			}
+			NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
+				network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
+			}
 			NodeGraphMessage::PasteNodes { serialized_nodes } => {
 				let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
 					Ok(d) => d,
diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs
index 5a61d28e3f..1ef32ba91b 100644
--- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs
+++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs
@@ -88,6 +88,10 @@ impl DocumentMetadata {
 		self.upstream_transforms.get(&node_id).copied().map(|(_, transform)| transform).unwrap_or(DAffine2::IDENTITY)
 	}
 
+	pub fn downstream_transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
+		self.document_to_viewport.inverse() * self.downstream_transform_to_viewport(layer)
+	}
+
 	pub fn downstream_transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
 		if layer == LayerNodeIdentifier::ROOT_PARENT {
 			return self.transform_to_viewport(layer);
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 9007c8c72a..4e347ef8b5 100644
--- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs
+++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs
@@ -4894,6 +4894,32 @@ impl NodeNetworkInterface {
 			self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);
 			self.set_chain_position(node_id, network_path);
 		} else {
+			// TODO: Implement a more robust horizontal shift system when inserting a node into a chain.
+			// This should be done by breaking the chain and shifting the sole dependents for each node upstream of the insertion.
+			// Before inserting the node, shift the layer right 7 units so that all sole dependents are also shifted
+			// let input_connector = InputConnector::node(parent.to_node(), 0);
+			// let old_upstream = self.upstream_output_connector(&input_connector, network_path);
+			// This also needs to disconnect from the downstream layer
+			// self.disconnect_input(&input_connector, network_path);
+			// let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
+			// 	log::error!("Could not get selected nodes in move_layer_to_stack");
+			// 	return;
+			// };
+			// let old_selected_nodes = selected_nodes.replace_with(vec![parent.to_node()]);
+
+			// for _ in 0..7 {
+			// 	self.shift_selected_nodes(Direction::Left, false, network_path);
+			// }
+			// // Grip drag it back to the right
+			// for _ in 0..7 {
+			// 	self.shift_selected_nodes(Direction::Right, true, network_path);
+			// }
+			// let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
+			// if let Some(old_upstream) = old_upstream {
+			// 	self.create_wire(&old_upstream, &input_connector, network_path);
+			// }
+
+			// Insert the node in the gap and set the upstream to a chain
 			self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);
 			self.force_set_upstream_to_chain(node_id, network_path);
 		}
diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs
index 21aed42521..ba46cebf0d 100644
--- a/editor/src/messages/tool/common_functionality/utility_functions.rs
+++ b/editor/src/messages/tool/common_functionality/utility_functions.rs
@@ -6,14 +6,14 @@ use graphene_std::vector::PointId;
 use glam::DVec2;
 
 /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
-pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
+pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
 	let mut best = None;
 	let mut best_distance_squared = tolerance * tolerance;
-
-	for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
+	for layer in layers {
 		let viewspace = document.metadata().transform_to_viewport(layer);
-
-		let vector_data = document.network_interface.compute_modified_vector(layer)?;
+		let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
+			continue;
+		};
 		for id in vector_data.single_connected_points() {
 			let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
 
diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs
index 0937efbef8..fb820b7664 100644
--- a/editor/src/messages/tool/tool_messages/freehand_tool.rs
+++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs
@@ -211,7 +211,8 @@ impl Fsm for FreehandToolFsmState {
 				tool_data.weight = tool_options.line_weight;
 
 				// Extend an endpoint of the selected path
-				if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
+				let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+				if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
 					tool_data.layer = Some(layer);
 					tool_data.end_point = Some((position, point));
 
diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs
index b1ff2158fa..827b4d8315 100644
--- a/editor/src/messages/tool/tool_messages/pen_tool.rs
+++ b/editor/src/messages/tool/tool_messages/pen_tool.rs
@@ -1,9 +1,10 @@
 use super::tool_prelude::*;
 use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
-use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
+use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type};
 use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
 use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
 use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
+use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
 use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
 use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
 use crate::messages::tool::common_functionality::graph_modification_utils;
@@ -57,6 +58,7 @@ pub enum PenToolMessage {
 	Redo,
 	Undo,
 	UpdateOptions(PenOptionsUpdate),
+	RecalculateLatestPointsPosition,
 }
 
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -202,7 +204,6 @@ struct LastPoint {
 
 #[derive(Clone, Debug, Default)]
 struct PenToolData {
-	layer: Option<LayerNodeIdentifier>,
 	snap_manager: SnapManager,
 	latest_points: Vec<LastPoint>,
 	point_index: usize,
@@ -215,6 +216,8 @@ struct PenToolData {
 	angle: f64,
 	auto_panning: AutoPanning,
 	modifiers: ModifierState,
+
+	buffering_merged_vector: bool,
 }
 impl PenToolData {
 	fn latest_point(&self) -> Option<&LastPoint> {
@@ -231,6 +234,24 @@ impl PenToolData {
 		self.latest_points.push(point);
 	}
 
+	// When the vector data transform changes, the positions of the points must be recalculated.
+	fn recalculate_latest_points_position(&mut self, document: &DocumentMessageHandler) {
+		let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+		let mut selected_layers = selected_nodes.selected_layers(document.metadata());
+		if let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) {
+			let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
+				return;
+			};
+			for point in &mut self.latest_points {
+				let Some(pos) = vector_data.point_domain.position_from_id(point.id) else {
+					continue;
+				};
+				point.pos = pos;
+				point.handle_start = point.pos;
+			}
+		}
+	}
+
 	/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
 	fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2) {
 		self.g1_continuous = true;
@@ -266,7 +287,9 @@ impl PenToolData {
 
 		// Get close path
 		let mut end = None;
-		let layer = self.layer?;
+		let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+		let mut selected_layers = selected_nodes.selected_layers(document.metadata());
+		let layer = selected_layers.next().filter(|_| selected_layers.next().is_none())?;
 		let vector_data = document.network_interface.compute_modified_vector(layer)?;
 		let start = self.latest_point()?.id;
 		let transform = document.metadata().document_to_viewport * transform;
@@ -426,13 +449,13 @@ impl Fsm for PenToolFsmState {
 			..
 		} = tool_action_data;
 
-		let mut transform = tool_data.layer.map(|layer| document.metadata().transform_to_document(layer)).unwrap_or_default();
+		let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+		let mut selected_layers = selected_nodes.selected_layers(document.metadata());
+		let layer = selected_layers.next().filter(|_| selected_layers.next().is_none());
+		let mut transform = layer.map(|layer| document.metadata().transform_to_document(layer)).unwrap_or_default();
 
 		if !transform.inverse().is_finite() {
-			let parent_transform = tool_data
-				.layer
-				.and_then(|layer| layer.parent(document.metadata()))
-				.map(|layer| document.metadata().transform_to_document(layer));
+			let parent_transform = layer.and_then(|layer| layer.parent(document.metadata())).map(|layer| document.metadata().transform_to_document(layer));
 
 			transform = parent_transform.unwrap_or(DAffine2::IDENTITY);
 		}
@@ -511,19 +534,23 @@ impl Fsm for PenToolFsmState {
 				let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
 				let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
 				let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
-
+				let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+				let mut selected_layers = selected_nodes.selected_layers(document.metadata());
 				// Perform extension of an existing path
-				if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE) {
+				let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+				if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
+					log::debug!("Should extend: {:?}", layer);
 					tool_data.add_point(LastPoint {
 						id: point,
 						pos: position,
 						in_segment: None,
 						handle_start: position,
 					});
-					tool_data.layer = Some(layer);
+					responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
 					tool_data.next_point = position;
 					tool_data.next_handle_start = position;
-				} else if let Some(layer) = tool_data.layer {
+				} else if let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) {
+					log::debug!("Adding to layer: {:?}", layer);
 					// Add the first point to a new layer
 					// Generate first point
 					let id = PointId::generate();
@@ -539,6 +566,7 @@ impl Fsm for PenToolFsmState {
 					tool_data.next_point = pos;
 					tool_data.next_handle_start = pos;
 				} else {
+					log::debug!("Creating new layer");
 					// New path layer
 					let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
 					let nodes = vec![(NodeId(0), node_type.default_node_template())];
@@ -547,7 +575,7 @@ impl Fsm for PenToolFsmState {
 					let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
 					tool_options.fill.apply_fill(layer, responses);
 					tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
-					tool_data.layer = Some(layer);
+					responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
 					responses.add(Message::StartBuffer);
 					responses.add(PenToolMessage::DragStart);
 					return PenToolFsmState::Ready;
@@ -556,12 +584,135 @@ impl Fsm for PenToolFsmState {
 				// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
 				PenToolFsmState::DraggingHandle
 			}
+			(state, PenToolMessage::RecalculateLatestPointsPosition) => {
+				tool_data.recalculate_latest_points_position(document);
+				state
+			}
 			(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
-				if tool_data.handle_end.is_some() {
-					responses.add(DocumentMessage::StartTransaction);
+				let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
+				let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
+				let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
+				// Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector)
+				if tool_data.buffering_merged_vector {
+					tool_data.buffering_merged_vector = false;
+					tool_data.bend_from_previous_point(SnapData::new(document, input), transform);
+					tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses);
+					tool_data.buffering_merged_vector = false;
+					PenToolFsmState::DraggingHandle
+				} else {
+					if tool_data.handle_end.is_some() {
+						responses.add(DocumentMessage::StartTransaction);
+					}
+					// Merge two layers if the point is connected to the end point of another path
+
+					// This might not be the correct solution to artboards being included as the other layer, which occurs due to the compute_modified_vector call in should_extend using the click targets for a layer instead of vector data.
+					let layers = LayerNodeIdentifier::ROOT_PARENT
+						.descendants(document.metadata())
+						.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
+					if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers) {
+						let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
+						let mut selected_layers = selected_nodes.selected_layers(document.metadata());
+						if let Some(current_layer) = selected_layers.next().filter(|current_layer| selected_layers.next().is_none() && *current_layer != other_layer) {
+							// Calculate the downstream transforms in order to bring the other vector data into the same layer space
+							let current_transform = document.metadata().downstream_transform_to_document(current_layer);
+							let other_transform = document.metadata().downstream_transform_to_document(other_layer);
+							// Represents the change in position that would occur if the other layer was moved below the current layer
+							let transform_delta = current_transform * other_transform.inverse();
+							let offset = transform_delta.inverse();
+							responses.add(GraphOperationMessage::TransformChange {
+								layer: other_layer,
+								transform: offset,
+								transform_in: crate::messages::portfolio::document::graph_operation::utility_types::TransformIn::Local,
+								skip_rerender: false,
+							});
+
+							// Move the other layer below the current layer for positioning purposes
+							let current_layer_parent = current_layer.parent(document.metadata()).unwrap();
+							let current_layer_index = current_layer_parent.children(document.metadata()).position(|child| child == current_layer).unwrap();
+							responses.add(NodeGraphMessage::MoveLayerToStack {
+								layer: other_layer,
+								parent: current_layer_parent,
+								insert_index: current_layer_index + 1,
+							});
+
+							// Merge the inputs of the two layers
+							let merge_node_id = NodeId::new();
+							let merge_node = document_node_definitions::resolve_document_node_type("Merge")
+								.expect("Failed to create merge node")
+								.default_node_template();
+							responses.add(NodeGraphMessage::InsertNode {
+								node_id: merge_node_id,
+								node_template: merge_node,
+							});
+							responses.add(NodeGraphMessage::SetToNodeOrLayer {
+								node_id: merge_node_id,
+								is_layer: false,
+							});
+							responses.add(NodeGraphMessage::MoveNodeToChainStart {
+								node_id: merge_node_id,
+								parent: current_layer,
+							});
+							responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
+								downstream_input: InputConnector::node(other_layer.to_node(), 1),
+								input_connector: InputConnector::node(merge_node_id, 1),
+							});
+							responses.add(NodeGraphMessage::DeleteNodes {
+								node_ids: vec![other_layer.to_node()],
+								delete_children: false,
+							});
+
+							// Add a flatten vector elements node after the merge
+							let flatten_node_id = NodeId::new();
+							let flatten_node = document_node_definitions::resolve_document_node_type("Flatten Vector Elements")
+								.expect("Failed to create flatten node")
+								.default_node_template();
+							responses.add(NodeGraphMessage::InsertNode {
+								node_id: flatten_node_id,
+								node_template: flatten_node,
+							});
+							responses.add(NodeGraphMessage::MoveNodeToChainStart {
+								node_id: flatten_node_id,
+								parent: current_layer,
+							});
+
+							// Add a path node after the flatten node
+							let path_node_id = NodeId::new();
+							let path_node = document_node_definitions::resolve_document_node_type("Path")
+								.expect("Failed to create path node")
+								.default_node_template();
+							responses.add(NodeGraphMessage::InsertNode {
+								node_id: path_node_id,
+								node_template: path_node,
+							});
+							responses.add(NodeGraphMessage::MoveNodeToChainStart {
+								node_id: path_node_id,
+								parent: current_layer,
+							});
+
+							// Add a transform node to ensure correct tooling modifications
+							let transform_node_id = NodeId::new();
+							let transform_node = document_node_definitions::resolve_document_node_type("Transform")
+								.expect("Failed to create transform node")
+								.default_node_template();
+							responses.add(NodeGraphMessage::InsertNode {
+								node_id: transform_node_id,
+								node_template: transform_node,
+							});
+							responses.add(NodeGraphMessage::MoveNodeToChainStart {
+								node_id: transform_node_id,
+								parent: current_layer,
+							});
+
+							responses.add(NodeGraphMessage::RunDocumentGraph);
+							responses.add(Message::StartBuffer);
+							responses.add(PenToolMessage::RecalculateLatestPointsPosition);
+						}
+					}
+					// Even if no buffer was started, the message still has to be run again in order to call bend_from_previous_point
+					tool_data.buffering_merged_vector = true;
+					responses.add(PenToolMessage::DragStart);
+					PenToolFsmState::PlacingAnchor
 				}
-				tool_data.bend_from_previous_point(SnapData::new(document, input), transform);
-				PenToolFsmState::DraggingHandle
 			}
 			(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data
 				.finish_placing_handle(SnapData::new(document, input), transform, responses)
@@ -633,7 +784,7 @@ impl Fsm for PenToolFsmState {
 			}
 			(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
 				responses.add(DocumentMessage::EndTransaction);
-				tool_data.layer = None;
+				responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() });
 				tool_data.handle_end = None;
 				tool_data.latest_points.clear();
 				tool_data.point_index = 0;


Test patch: