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 2dff2fbec..9fa5becf8 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> 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 b2f12435d..7f1096734 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, @@ -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 80ba4513c..9ef034a28 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> 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> 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::>(&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 5a61d28e3..1ef32ba91 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 9007c8c72..4e347ef8b 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 21aed4252..ba46cebf0 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) -> 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 0937efbef..fb820b766 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 b1ff2158f..827b4d831 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, snap_manager: SnapManager, latest_points: Vec, 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;