diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 18c6c0b4..c024ecab 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -10,34 +10,62 @@ use graphene_core::raster::image::ImageFrame; use graphene_core::raster::BlendMode; use graphene_core::text::{Font, TypesettingConfig}; use graphene_core::vector::style::Gradient; -use graphene_core::vector::PointId; use graphene_core::Color; +use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType}; use glam::DVec2; use std::collections::VecDeque; -pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeIdentifier, other_layer: LayerNodeIdentifier, responses: &mut VecDeque) { +/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists. +pub fn find_spline(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> Option { + document + .network_interface + .upstream_flow_back_from_nodes([layer.to_node()].to_vec(), &[], FlowType::HorizontalFlow) + .map(|node_id| (document.network_interface.reference(&node_id, &[]).unwrap(), node_id)) + .take_while(|(reference, _)| reference.as_ref().is_some_and(|node_ref| node_ref != "Path")) + .find(|(reference, _)| reference.as_ref().is_some_and(|node_ref| node_ref == "Spline")) + .map(|node| node.1) +} + +/// Merge `second_layer` to the `first_layer`. +pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIdentifier, second_layer: LayerNodeIdentifier, responses: &mut VecDeque) { + if first_layer == second_layer { + return; + } // 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); + let first_layer_transform = document.metadata().downstream_transform_to_document(first_layer); + let second_layer_transform = document.metadata().downstream_transform_to_document(second_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 transform_delta = first_layer_transform * second_layer_transform.inverse(); let offset = transform_delta.inverse(); responses.add(GraphOperationMessage::TransformChange { - layer: other_layer, + layer: second_layer, transform: offset, transform_in: 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(); + let mut current_and_other_layer_is_spline = false; + + match (find_spline(document, first_layer), find_spline(document, second_layer)) { + (Some(current_layer_spline), Some(other_layer_spline)) => { + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: [current_layer_spline, other_layer_spline].to_vec(), + delete_children: false, + }); + current_and_other_layer_is_spline = true; + } + _ => {} + } + + // Move the `second_layer` below the `first_layer` for positioning purposes + let first_layer_parent = first_layer.parent(document.metadata()).unwrap(); + let first_layer_index = first_layer_parent.children(document.metadata()).position(|child| child == first_layer).unwrap(); responses.add(NodeGraphMessage::MoveLayerToStack { - layer: other_layer, - parent: current_layer_parent, - insert_index: current_layer_index + 1, + layer: second_layer, + parent: first_layer_parent, + insert_index: first_layer_index + 1, }); // Merge the inputs of the two layers @@ -55,14 +83,14 @@ pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeI }); responses.add(NodeGraphMessage::MoveNodeToChainStart { node_id: merge_node_id, - parent: current_layer, + parent: first_layer, }); responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput { - downstream_input: InputConnector::node(other_layer.to_node(), 1), + downstream_input: InputConnector::node(second_layer.to_node(), 1), input_connector: InputConnector::node(merge_node_id, 1), }); responses.add(NodeGraphMessage::DeleteNodes { - node_ids: vec![other_layer.to_node()], + node_ids: vec![second_layer.to_node()], delete_children: false, }); @@ -77,7 +105,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeI }); responses.add(NodeGraphMessage::MoveNodeToChainStart { node_id: flatten_node_id, - parent: current_layer, + parent: first_layer, }); // Add a path node after the flatten node @@ -91,9 +119,25 @@ pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeI }); responses.add(NodeGraphMessage::MoveNodeToChainStart { node_id: path_node_id, - parent: current_layer, + parent: first_layer, }); + // Add a Spline node after the Path node if both the layers we are merging is spline. + if current_and_other_layer_is_spline { + let spline_node_id = NodeId::new(); + let spline_node = document_node_definitions::resolve_document_node_type("Spline") + .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: first_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") @@ -105,7 +149,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeI }); responses.add(NodeGraphMessage::MoveNodeToChainStart { node_id: transform_node_id, - parent: current_layer, + parent: first_layer, }); responses.add(NodeGraphMessage::RunDocumentGraph); @@ -113,6 +157,49 @@ pub fn merge_layers(document: &DocumentMessageHandler, current_layer: LayerNodeI responses.add(PenToolMessage::RecalculateLatestPointsPosition); } +/// Merge the `first_endpoint` with `second_endpoint`. +pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque) { + let transform = document.metadata().transform_to_document(layer); + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return }; + + let segment = vector_data.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont); + let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else { + log::error!("Could not get the segment for second_endpoint."); + return; + }; + + let mut handles = [None; 2]; + if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data) { + let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector_data).unwrap(); + let handle_position = transform.transform_point2(handle_position); + let anchor_position = transform.transform_point2(anchor_position); + let anchor_to_handle = handle_position - anchor_position; + handles[0] = Some(anchor_to_handle); + } + if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector_data) { + let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector_data).unwrap(); + let handle_position = transform.transform_point2(handle_position); + let anchor_position = transform.transform_point2(anchor_position); + let anchor_to_handle = handle_position - anchor_position; + handles[1] = Some(anchor_to_handle); + } + + if segment_start_point == second_endpont { + core::mem::swap(&mut segment_start_point, &mut segment_end_point); + handles.reverse(); + } + + let modification_type = VectorModificationType::RemovePoint { id: second_endpont }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + let modification_type = VectorModificationType::RemoveSegment { id: segment }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + let points = [segment_start_point, first_endpoint]; + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points, handles }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); +} + /// Create a new vector layer. pub fn new_vector_layer(subpaths: Vec>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque) -> LayerNodeIdentifier { let insert_index = 0; diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 044cf915..3c217f96 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,14 +1,12 @@ use super::tool_prelude::*; use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE}; -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::node_graph::document_node_definitions::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::{self}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points}; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; @@ -52,6 +50,7 @@ pub enum SplineToolMessage { Confirm, DragStart { append_to_selected: Key }, DragStop, + MergeEndpoints, PointerMove, PointerOutsideViewport, Undo, @@ -63,6 +62,7 @@ enum SplineToolFsmState { #[default] Ready, Drawing, + MergingEndpoints, } #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] @@ -168,6 +168,9 @@ impl<'a> MessageHandler> for SplineT Confirm, Abort, ), + SplineToolFsmState::MergingEndpoints => actions!(SplineToolMessageDiscriminant; + MergeEndpoints, + ), } } } @@ -196,26 +199,31 @@ struct SplineToolData { preview_segment: Option, extend: bool, weight: f64, - layer: Option, - starting_layer: Option, + /// The layer we are editing. + current_layer: Option, + /// 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)>, snap_manager: SnapManager, auto_panning: AutoPanning, } impl SplineToolData { fn cleanup(&mut self) { - self.layer = None; + self.current_layer = None; + self.start_merge_layer = None; + self.merge_endpoints = Vec::new(); self.preview_point = None; self.preview_segment = None; self.extend = false; self.points = Vec::new(); - self.starting_layer = None; } /// Get the snapped point while ignoring current layer fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> SnappedPoint { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] }; + let ignore = if let Some(layer) = self.current_layer { vec![layer] } else { vec![] }; let snap_data = SnapData::ignore(document, input, &ignore); self.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()) } @@ -243,9 +251,22 @@ impl Fsm for SplineToolFsmState { tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } + (SplineToolFsmState::MergingEndpoints, SplineToolMessage::MergeEndpoints) => { + let Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready }; + + 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); + + SplineToolFsmState::Ready + } (SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => { responses.add(DocumentMessage::StartTransaction); + tool_data.snap_manager.cleanup(responses); tool_data.cleanup(); tool_data.weight = tool_options.line_weight; @@ -253,38 +274,38 @@ 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); - } + let layers = LayerNodeIdentifier::ROOT_PARENT + .descendants(document.metadata()) + .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); // 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) { - tool_data.layer = Some(layer); - tool_data.points.push((point, position)); - tool_data.next_point = position; - tool_data.extend = true; + if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, layers, preferences) { + if find_spline(document, layer).is_some() { + // If the point is the part of Spline then we extend it. + tool_data.current_layer = Some(layer); + tool_data.points.push((point, position)); + tool_data.next_point = position; + tool_data.extend = true; - extend_spline(tool_data, true, responses); + extend_spline(tool_data, true, responses); - return SplineToolFsmState::Drawing; + 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)); + } } - // Create new path in the same layer when shift is down - if input.keyboard.key(append_to_selected) { - let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); - let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none()); - if let Some(layer) = existing_layer { - tool_data.layer = Some(layer); + let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); + let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); + let selected_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none()); + + let append_to_selected_layer = input.keyboard.key(append_to_selected); + + // Create new path in the selected layer when shift is down + match (selected_layer, append_to_selected_layer) { + (Some(layer), true) => { + tool_data.current_layer = Some(layer); let transform = document.metadata().transform_to_viewport(layer); let position = transform.inverse().transform_point2(input.mouse.position); @@ -292,6 +313,7 @@ impl Fsm for SplineToolFsmState { return SplineToolFsmState::Drawing; } + _ => {} } responses.add(DocumentMessage::DeselectAllLayers); @@ -307,7 +329,7 @@ impl Fsm for SplineToolFsmState { 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_data.weight, layer, responses); - tool_data.layer = Some(layer); + tool_data.current_layer = Some(layer); responses.add(Message::StartBuffer); @@ -319,13 +341,9 @@ impl Fsm for SplineToolFsmState { tool_data.extend = false; return SplineToolFsmState::Drawing; } - if tool_data.layer.is_none() { + if tool_data.current_layer.is_none() { return SplineToolFsmState::Ready; }; - if join_path(document, input.mouse.position, tool_data, preferences, responses) { - responses.add(DocumentMessage::EndTransaction); - return SplineToolFsmState::Ready; - } 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) { extend_spline(tool_data, false, responses); @@ -334,7 +352,7 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => { - let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready }; + let Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready }; let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp); let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore, preferences); @@ -374,17 +392,16 @@ impl Fsm for SplineToolFsmState { state } - (SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => { + (SplineToolFsmState::Drawing, SplineToolMessage::Confirm) => { if tool_data.points.len() >= 2 { delete_preview(tool_data, responses); - responses.add(DocumentMessage::EndTransaction); - } else { - responses.add(DocumentMessage::AbortTransaction); + merge_paths_layer(document, tool_data, preferences, responses); } - - tool_data.snap_manager.cleanup(responses); - tool_data.cleanup(); - + responses.add(SplineToolMessage::MergeEndpoints); + SplineToolFsmState::MergingEndpoints + } + (SplineToolFsmState::Drawing, SplineToolMessage::Abort) => { + responses.add(DocumentMessage::AbortTransaction); SplineToolFsmState::Ready } (_, SplineToolMessage::WorkingColorChanged) => { @@ -409,6 +426,7 @@ impl Fsm for SplineToolFsmState { HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]), HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]), ]), + SplineToolFsmState::MergingEndpoints => HintData(vec![]), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); @@ -419,144 +437,56 @@ 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 }; +fn merge_paths_layer(document: &DocumentMessageHandler, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque) { + if tool_data.points.len() < 2 { + return; + }; + let (last_endpoint, last_endpoint_position) = tool_data.points.last().unwrap(); + let (start_endpoint, _) = tool_data.points.first().unwrap(); let preview_point = tool_data.preview_point; + let Some(current_layer) = tool_data.current_layer else { return }; - // 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, - LayerNodeIdentifier::ROOT_PARENT.descendants(document.metadata()), - |cp| preview_point.is_some_and(|pp| pp == cp) || cp == endpoint, - preferences, - ); - let Some((other_layer, join_point, _)) = closest_point else { return false }; + let layers = LayerNodeIdentifier::ROOT_PARENT + .descendants(document.metadata()) + .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); - // 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 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); - // 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, - }); + let start_merge_layer = tool_data.start_merge_layer; + let end_merge_layer = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude, preferences); - return true; + 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, } - 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 }, - }); - } + 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)); } - - true } fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque) { delete_preview(tool_data, responses); - let Some(layer) = tool_data.layer else { return }; + let Some(layer) = tool_data.current_layer else { return }; let next_point_pos = tool_data.next_point; let next_point_id = PointId::generate(); @@ -585,7 +515,7 @@ fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: } fn delete_preview(tool_data: &mut SplineToolData, responses: &mut VecDeque) { - let Some(layer) = tool_data.layer else { return }; + let Some(layer) = tool_data.current_layer else { return }; if let Some(id) = tool_data.preview_point { let modification_type = VectorModificationType::RemovePoint { id }; @@ -599,426 +529,3 @@ 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 -}