Implement merging pairs of {paths, splines} with the Pen and Spline tools (#2292)

* refactor

* impl find_spline function

* impl merge_layers() to merge two spline layer to one spline layer

* impl merging spline to another spline which are not in the same layer

* impl merging of spline with path

* impl merge spline start endpoint and last endpoint

* fix naming

* fix handle transformation

* refactor

* fix merging with path with only one segment

* refactor

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Priyanshu
2025-02-21 17:43:49 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent fb13d58767
commit 674db97dc4
2 changed files with 211 additions and 617 deletions
@@ -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<Message>) {
/// 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<NodeId> {
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<Message>) {
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<Message>) {
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<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0;