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-22 07:13:49 +05:30 committed by GitHub
parent fb13d58767
commit 674db97dc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 211 additions and 617 deletions

View File

@ -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;

View File

@ -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<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineT
Confirm,
Abort,
),
SplineToolFsmState::MergingEndpoints => actions!(SplineToolMessageDiscriminant;
MergeEndpoints,
),
}
}
}
@ -196,26 +199,31 @@ struct SplineToolData {
preview_segment: Option<SegmentId>,
extend: bool,
weight: f64,
layer: Option<LayerNodeIdentifier>,
starting_layer: Option<LayerNodeIdentifier>,
/// The layer we are editing.
current_layer: Option<LayerNodeIdentifier>,
/// The layer and endpoint to merge with the spline's starting endpoint after drawing is complete.
start_merge_layer: Option<(LayerNodeIdentifier, PointId)>,
/// The endpoint pairs to merge after we have finished drawing the spline and merged the layers.
merge_endpoints: Vec<(PointId, PointId)>,
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<Message>) -> 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<Message>) {
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<Message>) {
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<Message>) {
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<Messa
tool_data.preview_point = None;
tool_data.preview_segment = None;
}
fn merge_two_spline_layer(document: &DocumentMessageHandler, current_layer: LayerNodeIdentifier, other_layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
// 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::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// 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<Message>,
) {
// 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::<Vec<_>>();
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::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// 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<Message>) {
// 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::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// 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::<Vec<NodeId>>();
// 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::<Vec<NodeId>>();
// 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
}