diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index d7eb508c..044cf915 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,12 +1,14 @@ use super::tool_prelude::*; use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE}; -use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type}; use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_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::{FlowType, 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; +use crate::messages::tool::common_functionality::graph_modification_utils::{self}; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; @@ -195,6 +197,7 @@ struct SplineToolData { extend: bool, weight: f64, layer: Option, + starting_layer: Option, snap_manager: SnapManager, auto_panning: AutoPanning, } @@ -206,6 +209,7 @@ impl SplineToolData { self.preview_segment = None; self.extend = false; self.points = Vec::new(); + self.starting_layer = None; } /// Get the snapped point while ignoring current layer @@ -249,6 +253,19 @@ impl Fsm for SplineToolFsmState { let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + // Check if we're starting from an endpoint of any layer, even if not extending + let closest_endpoint = closest_point( + document, + viewport, + PATH_JOIN_THRESHOLD, + LayerNodeIdentifier::ROOT_PARENT.descendants(document.metadata()), + |_| false, // Don't exclude any points + preferences, + ); + if let Some((start_layer, _, _)) = closest_endpoint { + tool_data.starting_layer = Some(start_layer); + } + // Extend an endpoint of the selected path let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata()), preferences) { @@ -405,29 +422,133 @@ impl Fsm for SplineToolFsmState { /// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque) -> bool { let Some(&(endpoint, _)) = tool_data.points.last() else { return false }; - + let Some(&(start_point, _)) = tool_data.points.first() else { return false }; + let Some(starting_layer) = tool_data.starting_layer else { return false }; + let Some(current_layer) = tool_data.layer else { return false }; let preview_point = tool_data.preview_point; - let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); - let selected_layers = selected_nodes.selected_layers(document.metadata()); // Get the closest point to mouse position which is not preview_point or end_point. let closest_point = closest_point( document, mouse_pos, PATH_JOIN_THRESHOLD, - selected_layers, + LayerNodeIdentifier::ROOT_PARENT.descendants(document.metadata()), |cp| preview_point.is_some_and(|pp| pp == cp) || cp == endpoint, preferences, ); - let Some((layer, join_point, _)) = closest_point else { return false }; + let Some((other_layer, join_point, _)) = closest_point else { return false }; // Last end point inserted was the preview point and segment therefore we delete it before joining the end_point & join_point. delete_preview(tool_data, responses); - let points = [endpoint, join_point]; - let id = SegmentId::generate(); - let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + // If the points are in different layers, merge them first + if current_layer == other_layer { + // If points are in the same layer, just connect them + let points = [endpoint, join_point]; + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; + responses.add(GraphOperationMessage::Vector { + layer: current_layer, + modification_type, + }); + + return true; + } + + match (is_layer_spline(document, starting_layer), is_layer_spline(document, other_layer)) { + (true, true) => { + merge_two_spline_layer(document, current_layer, other_layer, responses); + let points = [endpoint, join_point]; + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; + responses.add(GraphOperationMessage::Vector { + layer: current_layer, + modification_type, + }); + } + (false, false) => { + let Some(current_vector_data) = document.network_interface.compute_modified_vector(current_layer) else { + log::error!("Could not get vector data for current layer"); + return false; + }; + let Some(starting_vector_data) = document.network_interface.compute_modified_vector(starting_layer) else { + log::error!("Could not get vector data for other layer"); + return false; + }; + + let Some(starting_layer_endpoint) = starting_vector_data.end_point().last() else { + log::error!("Could not get endpoint"); + return false; + }; + + let handles = (0..current_vector_data.segment_domain.handles().len()) + .find_map(|index| { + let (start_id, end_id, bezier) = current_vector_data.segment_points_from_index(index); + if start_id == endpoint { + Some([bezier.handles.start(), bezier.handles.end()]) + } else if end_id == endpoint { + // Reverse the handles if connecting to end point + Some([bezier.handles.end(), bezier.handles.start()]) + } else { + None + } + }) + .unwrap(); + + // Merge the layers first + merge_non_spline_layers(document, starting_layer, current_layer, other_layer, responses); + + let points = [endpoint, join_point]; + let points2 = [start_point, starting_layer_endpoint]; + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points, handles }; + responses.add(GraphOperationMessage::Vector { + layer: starting_layer, + modification_type, + }); + + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points: points2, handles }; + responses.add(GraphOperationMessage::Vector { + layer: starting_layer, + modification_type, + }); + } + _ => { + let current_vector_data = match document.network_interface.compute_modified_vector(current_layer) { + Some(data) => data, + None => { + log::error!("Could not get vector data for current layer"); + return false; + } + }; + + let handles = (0..current_vector_data.segment_domain.handles().len()).find_map(|index| { + let (start_id, end_id, bezier) = current_vector_data.segment_points_from_index(index); + if start_id == endpoint { + Some([bezier.handles.start(), bezier.handles.end()]) + } else if end_id == endpoint { + Some([bezier.handles.end(), bezier.handles.start()]) + } else { + None + } + }); + + let Some(handles) = handles else { + log::error!("Could not find handles for endpoint"); + return false; + }; + + merge_path_spline_layer(document, current_layer, other_layer, responses); + + let points = [endpoint, join_point]; + let id = SegmentId::generate(); + responses.add(GraphOperationMessage::Vector { + layer: other_layer, + modification_type: VectorModificationType::InsertSegment { id, points, handles }, + }); + } + } true } @@ -478,3 +599,426 @@ fn delete_preview(tool_data: &mut SplineToolData, responses: &mut VecDeque) { + // 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: TransformIn::Local, + skip_rerender: false, + }); + + // First find their IDs + let current_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes( + vec![current_layer.to_node()], + &[], + crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow, + ) + .collect::>(); + + let other_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow) + .collect::>(); + + // Add merge node and insert between path and spline + 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), + }); + + // Add flatten vector elements node after 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, + }); + + 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, + }); + + let spline_node_id = NodeId::new(); + let spline_node = document_node_definitions::resolve_document_node_type("Splines from Points") + .expect("Failed to create spline node") + .default_node_template(); + responses.add(NodeGraphMessage::InsertNode { + node_id: spline_node_id, + node_template: spline_node, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: spline_node_id, + parent: current_layer, + }); + + let stroke_node_id = NodeId::new(); + let stroke_node = document_node_definitions::resolve_document_node_type("Stroke") + .expect("Failed to create stroke node") + .default_node_template(); + responses.add(NodeGraphMessage::InsertNode { + node_id: stroke_node_id, + node_template: stroke_node, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: stroke_node_id, + parent: current_layer, + }); + + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: current_layer_nodes[1..3].to_vec(), + delete_children: false, + }); + + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: other_layer_nodes[..3].to_vec(), + delete_children: false, + }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(Message::StartBuffer); +} + +fn merge_non_spline_layers( + document: &DocumentMessageHandler, + starting_layer: LayerNodeIdentifier, + current_layer: LayerNodeIdentifier, + other_layer: LayerNodeIdentifier, + responses: &mut VecDeque, +) { + // 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: TransformIn::Local, + skip_rerender: false, + }); + + // Delete spline and stroke nodes from both layers + // First find their IDs + let starting_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes( + vec![starting_layer.to_node()], + &[], + crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow, + ) + .collect::>(); + + let current_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes( + vec![current_layer.to_node()], + &[], + crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow, + ) + .collect::>(); + + let other_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow) + .collect::>(); + + // Add merge node and insert between path and spline + 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: starting_layer, + }); + responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput { + downstream_input: InputConnector::node(other_layer.to_node(), 1), + input_connector: InputConnector::node(merge_node_id, 1), + }); + + let merge_node_id2 = NodeId::new(); + let merge_node2 = 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_id2, + node_template: merge_node2, + }); + responses.add(NodeGraphMessage::SetToNodeOrLayer { + node_id: merge_node_id2, + is_layer: false, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: merge_node_id2, + parent: starting_layer, + }); + responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput { + downstream_input: InputConnector::node(current_layer.to_node(), 1), + input_connector: InputConnector::node(merge_node_id2, 1), + }); + // Add flatten vector elements node after merge + let flatten_node_id2 = NodeId::new(); + let flatten_node2 = 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_id2, + node_template: flatten_node2, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: flatten_node_id2, + parent: starting_layer, + }); + + 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: starting_layer, + }); + + let stroke_node_id = NodeId::new(); + let stroke_node = document_node_definitions::resolve_document_node_type("Stroke") + .expect("Failed to create stroke node") + .default_node_template(); + responses.add(NodeGraphMessage::InsertNode { + node_id: stroke_node_id, + node_template: stroke_node, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: stroke_node_id, + parent: starting_layer, + }); + + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: starting_layer_nodes[1..(if is_layer_line(document, starting_layer) { 2 } else { 3 })].to_vec(), + delete_children: false, + }); + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: other_layer_nodes[..(if is_layer_line(document, other_layer) { 2 } else { 3 })].to_vec(), + delete_children: false, + }); + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: current_layer_nodes[..2].to_vec(), + delete_children: false, + }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(Message::StartBuffer); +} + +fn merge_path_spline_layer(document: &DocumentMessageHandler, current_layer: LayerNodeIdentifier, other_layer: LayerNodeIdentifier, responses: &mut VecDeque) { + // 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: TransformIn::Local, + skip_rerender: false, + }); + + // First find their IDs + let current_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes( + vec![current_layer.to_node()], + &[], + crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow, + ) + .collect::>(); + + let other_layer_nodes = document + .network_interface + .upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow) + .collect::>(); + + // Add merge node and insert between path and spline + 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: other_layer, + }); + + responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput { + downstream_input: InputConnector::node(current_layer.to_node(), 1), + input_connector: InputConnector::node(merge_node_id, 1), + }); + + // Add flatten vector elements node after 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: other_layer, + }); + + 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: other_layer, + }); + + let stroke_node_id = NodeId::new(); + let stroke_node = document_node_definitions::resolve_document_node_type("Stroke") + .expect("Failed to create stroke node") + .default_node_template(); + responses.add(NodeGraphMessage::InsertNode { + node_id: stroke_node_id, + node_template: stroke_node, + }); + responses.add(NodeGraphMessage::MoveNodeToChainStart { + node_id: stroke_node_id, + parent: other_layer, + }); + + let node_range = if is_layer_line(document, other_layer) { + 1..2 // transform node is not deleted + } else { + 1..3 + }; + + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: other_layer_nodes[node_range].to_vec(), + delete_children: false, + }); + + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: current_layer_nodes[..2].to_vec(), + delete_children: false, + }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(Message::StartBuffer); +} + +fn is_layer_spline(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool { + let nodes = document + .network_interface + .upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow) + .collect::>(); + + // Check node types in the chain + let mut has_spline = false; + + for node in nodes { + if let Some(reference) = document.network_interface.reference(&node, &[]) { + match reference.as_deref() { + Some("Splines from Points") => has_spline = true, + _ => continue, + } + } + } + + has_spline +} + +fn is_layer_line(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool { + let nodes = document + .network_interface + .upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow) + .collect::>(); + + // Check node types in the chain + let mut has_line = false; + + for node in nodes { + if let Some(reference) = document.network_interface.reference(&node, &[]) { + match reference.as_deref() { + Some("Line") => has_line = true, + _ => continue, + } + } + } + + has_line +}