Make joining path endpoints across layers work to merge the two layers (#2245)

* move merge_layers function to graph_modification_utils

* merge_layer before segment insertion

* Tidying up

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Sidharth-Singh10 2025-02-05 12:55:54 +05:30 committed by GitHub
parent 12cd0c33a3
commit b60af758a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 132 deletions

View File

@ -1,6 +1,9 @@
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate}; use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput}; use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::raster::image::ImageFrame; use graphene_core::raster::image::ImageFrame;
@ -13,6 +16,103 @@ use graphene_core::Color;
use glam::DVec2; use glam::DVec2;
use std::collections::VecDeque; use std::collections::VecDeque;
pub fn merge_layers(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,
});
// 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);
}
/// Create a new vector layer. /// Create a new vector layer.
pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;

View File

@ -1,4 +1,4 @@
use super::graph_modification_utils; use super::graph_modification_utils::{self, merge_layers};
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint}; use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource}; use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource};
@ -249,42 +249,17 @@ impl ShapeState {
handles: [None, None], handles: [None, None],
}; };
responses.add(GraphOperationMessage::Vector { layer: layer1, modification_type }); responses.add(GraphOperationMessage::Vector { layer: layer1, modification_type });
} } else {
// TODO: Fix the implementation of this case so it actually connects the separate layers, see: // Merge the layers
// TODO: <https://github.com/GraphiteEditor/Graphite/pull/2227#issuecomment-2626342475> merge_layers(document, layer1, layer2, responses);
else { // Create segment between the two points
// Points are in different layers - find the topmost layer
let top_layer = document.metadata().all_layers().find(|&layer| layer == layer1 || layer == layer2).unwrap_or(layer1);
let bottom_layer = if top_layer == layer1 { layer2 } else { layer1 };
let bottom_point = if top_layer == layer1 { end_point } else { start_point };
// Get position of point in bottom layer
let Some(bottom_vector_data) = document.network_interface.compute_modified_vector(bottom_layer) else {
return;
};
let Some(point_pos) = bottom_vector_data.point_domain.position_from_id(bottom_point) else {
return;
};
// Create new point in top layer
let new_point_id = PointId::generate();
let modification_type = VectorModificationType::InsertPoint {
id: new_point_id,
position: point_pos,
};
responses.add(GraphOperationMessage::Vector { layer: top_layer, modification_type });
// Create segment between points in top layer
let segment_id = SegmentId::generate(); let segment_id = SegmentId::generate();
let points = if top_layer == layer1 { [start_point, new_point_id] } else { [new_point_id, end_point] };
let modification_type = VectorModificationType::InsertSegment { let modification_type = VectorModificationType::InsertSegment {
id: segment_id, id: segment_id,
points, points: [end_point, start_point],
handles: [None, None], handles: [None, None],
}; };
responses.add(GraphOperationMessage::Vector { layer: top_layer, modification_type }); responses.add(GraphOperationMessage::Vector { layer: layer1, modification_type });
} }
return; return;
} }

View File

@ -1,13 +1,12 @@
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE}; use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
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_overlays; use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; 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::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::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::{self, merge_layers};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
use crate::messages::tool::common_functionality::utility_functions::should_extend; use crate::messages::tool::common_functionality::utility_functions::should_extend;
@ -1186,99 +1185,3 @@ impl Fsm for PenToolFsmState {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
} }
} }
fn merge_layers(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: 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);
}