Repository: GraphiteEditor/Graphite (language: rust)

Issue:
Make the Spline tool draw a polylines with a `Path -> Spline from Points` node setup
- [x] **Part 1** (#2200)

    Listen to the task description [in this video](https://files.keavon.com/-/ChillyTenseAdamsstaghornedbeetle/capture_16_.mp4).

- [x] **Part 2** (#2203)

1. Make the Spline tool visualize the endpoints of each spline path just like the Freehand tool does (even if there are multiple spline paths because the Path node has multiple subpaths)
2. Allow clicking on the endpoints of a spline (on either end), when not drawing, to begin drawing a continuation of the spline
3. Allow clicking the start point while drawing spline segments to add a segment which closes the path given to the Spline from Points node
4. Allow holding <kbd>Shift</kbd> to begin drawing a new spline subpath in the same layer, instead of making it add a new layer (just like the equivalent functionality with the Pen tool)

- [x] **Part 3** (#2274)

1. Create an upgrade script to transform a single existing "Spline" node, in the graph of any existing documents people have today, into a `Path -> Spline from Points` node pair with the point data transferred over to the Path node's geometry
2. Delete the "Spline" node
5. Rename the "Spline from Points" node to just "Spline"
6. Create an upgrade script to rename the "Spline from Points" node in existing documents to "Spline"

    Search the project for "document upgrade code" and you'll find all the comments relating to the upgrading scripts. The ones in `portfolio_message_handler.rs`, within the handler for the `PortfolioMessage::OpenDocumentFileWithId` message, are most relevant to read and that's likely where you'll be adding most or all the upgrading code to.

- [x] **Part 4** (PR 1 of 2: #2269, PR 2 of 2: #2319
  
  Merging path with splines, splines with paths, splines with splines, and paths with paths by joining their endpoints correctly. This is described as part of https://files.keavon.com/-/PunyUprightStagbeetle/capture_35_.mp4


Discussion / hints:
Can I take this issue as my first issue?
@kliwongan this is currently being worked on by @indierusty (here's the [Discord thread](https://discord.com/channels/731730685944922173/1327206573071667202)). If there isn't a viable PR opened within a week, feel free to ask again if it's available then.
@kliwongan I ended up adding a new part to this issue, so now it's a three-parter. Feel free to work on part 3, which isn't blocked by the previous two parts, if you would like. If you plan to work on it, please let me know when you'll be beginning.
Implemented Part 1 i.e. make spline tool to use Path and Spline from Points node instead of just Spline node to make spline tool further non-destructive in #2200 
Fixed part of part 4 in #2269

Fix patch:
diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs
index 3c217f9631..2fb1a1a6c4 100644
--- a/editor/src/messages/tool/tool_messages/spline_tool.rs
+++ b/editor/src/messages/tool/tool_messages/spline_tool.rs
@@ -187,6 +187,12 @@ impl ToolTransition for SplineTool {
 	}
 }
 
+#[derive(Clone, Debug)]
+enum EndpointPosition {
+	Start,
+	End,
+}
+
 #[derive(Clone, Debug, Default)]
 struct SplineToolData {
 	/// List of points inserted.
@@ -201,10 +207,10 @@ struct SplineToolData {
 	weight: f64,
 	/// The layer we are editing.
 	current_layer: Option<LayerNodeIdentifier>,
-	/// The layer and endpoint to merge with the spline's starting endpoint after drawing is complete.
-	start_merge_layer: Option<(LayerNodeIdentifier, PointId)>,
-	/// The endpoint pairs to merge after we have finished drawing the spline and merged the layers.
-	merge_endpoints: Vec<(PointId, PointId)>,
+	/// The layers to merge to the current layer before we merge endpoints in merge_endpoint field.
+	merge_layers: HashSet<LayerNodeIdentifier>,
+	/// The endpoint IDs to merge with the spline's start/end endpoint after spline drawing is finished.
+	merge_endpoints: Vec<(EndpointPosition, PointId)>,
 	snap_manager: SnapManager,
 	auto_panning: AutoPanning,
 }
@@ -212,7 +218,7 @@ struct SplineToolData {
 impl SplineToolData {
 	fn cleanup(&mut self) {
 		self.current_layer = None;
-		self.start_merge_layer = None;
+		self.merge_layers = HashSet::new();
 		self.merge_endpoints = Vec::new();
 		self.preview_point = None;
 		self.preview_segment = None;
@@ -252,15 +258,31 @@ impl Fsm for SplineToolFsmState {
 				self
 			}
 			(SplineToolFsmState::MergingEndpoints, SplineToolMessage::MergeEndpoints) => {
-				let Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready };
+				let Some(current_layer) = tool_data.current_layer else { return SplineToolFsmState::Ready };
+
+				if let Some(&layer) = tool_data.merge_layers.iter().last() {
+					merge_layers(document, current_layer, layer, responses);
+					tool_data.merge_layers.remove(&layer);
+
+					responses.add(SplineToolMessage::MergeEndpoints);
+					return SplineToolFsmState::MergingEndpoints;
+				}
+
+				let Some((start_endpoint, _)) = tool_data.points.first() else { return SplineToolFsmState::Ready };
+				let Some((last_endpoint, _)) = tool_data.points.last() else { return SplineToolFsmState::Ready };
+
+				if let Some((position, second_endpoint)) = tool_data.merge_endpoints.pop() {
+					let first_endpoint = match position {
+						EndpointPosition::Start => *start_endpoint,
+						EndpointPosition::End => *last_endpoint,
+					};
+					merge_points(document, current_layer, first_endpoint, second_endpoint, responses);
 
-				if let Some((first, second)) = tool_data.merge_endpoints.pop() {
-					merge_points(document, layer, first, second, responses);
 					responses.add(SplineToolMessage::MergeEndpoints);
 					return SplineToolFsmState::MergingEndpoints;
 				}
-				responses.add(DocumentMessage::EndTransaction);
 
+				responses.add(DocumentMessage::EndTransaction);
 				SplineToolFsmState::Ready
 			}
 			(SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => {
@@ -291,8 +313,8 @@ impl Fsm for SplineToolFsmState {
 
 						return SplineToolFsmState::Drawing;
 					} else {
-						// Otherwise we keep the layer identifier and endpoint identifier to merge it with the spline later when we finish drawing the spline.
-						tool_data.start_merge_layer = Some((layer, point));
+						tool_data.merge_layers.insert(layer);
+						tool_data.merge_endpoints.push((EndpointPosition::Start, point));
 					}
 				}
 
@@ -346,7 +368,13 @@ impl Fsm for SplineToolFsmState {
 				};
 				tool_data.next_point = tool_data.snapped_point(document, input).snapped_point_document;
 				if tool_data.points.last().map_or(true, |last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) {
+					let preview_point = tool_data.preview_point;
 					extend_spline(tool_data, false, responses);
+					tool_data.preview_point = preview_point;
+
+					if try_merging_lastest_endpoint(document, tool_data, preferences).is_some() {
+						responses.add(SplineToolMessage::Confirm);
+					}
 				}
 
 				SplineToolFsmState::Drawing
@@ -395,7 +423,6 @@ impl Fsm for SplineToolFsmState {
 			(SplineToolFsmState::Drawing, SplineToolMessage::Confirm) => {
 				if tool_data.points.len() >= 2 {
 					delete_preview(tool_data, responses);
-					merge_paths_layer(document, tool_data, preferences, responses);
 				}
 				responses.add(SplineToolMessage::MergeEndpoints);
 				SplineToolFsmState::MergingEndpoints
@@ -437,14 +464,13 @@ impl Fsm for SplineToolFsmState {
 	}
 }
 
-fn merge_paths_layer(document: &DocumentMessageHandler, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) {
+fn try_merging_lastest_endpoint(document: &DocumentMessageHandler, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler) -> Option<()> {
 	if tool_data.points.len() < 2 {
-		return;
+		return None;
 	};
-	let (last_endpoint, last_endpoint_position) = tool_data.points.last().unwrap();
-	let (start_endpoint, _) = tool_data.points.first().unwrap();
+	let (last_endpoint, last_endpoint_position) = tool_data.points.last()?;
 	let preview_point = tool_data.preview_point;
-	let Some(current_layer) = tool_data.current_layer else { return };
+	let current_layer = tool_data.current_layer?;
 
 	let layers = LayerNodeIdentifier::ROOT_PARENT
 		.descendants(document.metadata())
@@ -453,34 +479,11 @@ fn merge_paths_layer(document: &DocumentMessageHandler, tool_data: &mut SplineTo
 	let exclude = |p: PointId| preview_point.is_some_and(|pp| pp == p) || *last_endpoint == p;
 	let position = document.metadata().transform_to_viewport(current_layer).transform_point2(*last_endpoint_position);
 
-	let start_merge_layer = tool_data.start_merge_layer;
-	let end_merge_layer = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude, preferences);
+	let (layer, endpoint, _) = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude, preferences)?;
+	tool_data.merge_layers.insert(layer);
+	tool_data.merge_endpoints.push((EndpointPosition::End, endpoint));
 
-	let mut endpoints_to_merge = [None, None];
-
-	match (start_merge_layer, end_merge_layer) {
-		(Some(first), Some(last)) => {
-			merge_layers(document, first.0, last.0, responses);
-			merge_layers(document, current_layer, first.0, responses);
-			endpoints_to_merge = [first.1, last.1].map(|p| Some(p));
-		}
-		(Some(first), None) => {
-			endpoints_to_merge[0] = Some(first.1);
-			merge_layers(document, current_layer, first.0, responses);
-		}
-		(None, Some(last)) => {
-			endpoints_to_merge[1] = Some(last.1);
-			merge_layers(document, current_layer, last.0, responses);
-		}
-		(None, None) => return,
-	}
-
-	if let Some(merge_endpoint) = endpoints_to_merge[0] {
-		tool_data.merge_endpoints.push((*start_endpoint, merge_endpoint));
-	}
-	if let Some(merge_endpoint) = endpoints_to_merge[1] {
-		tool_data.merge_endpoints.push((*last_endpoint, merge_endpoint));
-	}
+	Some(())
 }
 
 fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {


Test patch: