Skip to content

Commit b9a1b2e

Browse files
4adexKeavon
andauthored
Add copy/cut/paste/duplicate functionality for path geometry (#2812)
* Copy and Paste for paths * Fix merge * Implement Copy, Cut and Duplicate * Fix selection of segments * Fix formatting * Code review --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent 34a8b9b commit b9a1b2e

File tree

8 files changed

+452
-12
lines changed

8 files changed

+452
-12
lines changed

editor/src/messages/input_mapper/input_mappings.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ pub fn input_mappings() -> Mapping {
211211
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
212212
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
213213
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
214+
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),
215+
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),
216+
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),
214217
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
215218
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),
216219
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),

editor/src/messages/portfolio/portfolio_message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ pub enum PortfolioMessage {
8888
PasteSerializedData {
8989
data: String,
9090
},
91+
PasteSerializedVector {
92+
data: String,
93+
},
9194
CenterPastedLayers {
9295
layers: Vec<LayerNodeIdentifier>,
9396
},

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::document::utility_types::network_interface;
33
use super::spreadsheet::SpreadsheetMessageHandler;
44
use super::utility_types::{PanelType, PersistentData};
55
use crate::application::generate_uuid;
6-
use crate::consts::DEFAULT_DOCUMENT_NAME;
6+
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
77
use crate::messages::animation::TimingInformation;
88
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
99
use crate::messages::dialog::simple_dialogs;
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
1212
use crate::messages::portfolio::document::DocumentMessageContext;
1313
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
1414
use crate::messages::portfolio::document::node_graph::document_node_definitions;
15+
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
1516
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
1617
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
1718
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
@@ -21,11 +22,14 @@ use crate::messages::prelude::*;
2122
use crate::messages::tool::common_functionality::graph_modification_utils;
2223
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
2324
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
25+
use bezier_rs::BezierHandles;
2426
use glam::{DAffine2, DVec2};
2527
use graph_craft::document::NodeId;
2628
use graph_craft::document::value::TaggedValue;
29+
use graphene_std::Color;
2730
use graphene_std::renderer::Quad;
2831
use graphene_std::text::Font;
32+
use graphene_std::vector::{HandleId, PointId, SegmentId, VectorData, VectorModificationType};
2933
use std::vec;
3034

3135
#[derive(ExtractField)]
@@ -576,6 +580,99 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
576580
}
577581
}
578582
}
583+
// Custom paste implementation for Path tool
584+
PortfolioMessage::PasteSerializedVector { data } => {
585+
// If using Path tool then send the operation to Path tool
586+
if *current_tool == ToolType::Path {
587+
responses.add(PathToolMessage::Paste { data });
588+
return;
589+
}
590+
591+
// If not using Path tool, create new layers and add paths into those
592+
if let Some(document) = self.active_document() {
593+
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) else {
594+
return;
595+
};
596+
597+
let mut layers = Vec::new();
598+
599+
for (_, new_vector, transform) in data {
600+
let Some(node_type) = resolve_document_node_type("Path") else {
601+
error!("Path node does not exist");
602+
continue;
603+
};
604+
let nodes = vec![(NodeId(0), node_type.default_node_template())];
605+
606+
let parent = document.new_layer_parent(false);
607+
608+
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
609+
layers.push(layer);
610+
611+
// Adding the transform back into the layer
612+
responses.add(GraphOperationMessage::TransformSet {
613+
layer,
614+
transform,
615+
transform_in: TransformIn::Local,
616+
skip_rerender: false,
617+
});
618+
619+
// Add default fill and stroke to the layer
620+
let fill_color = Color::WHITE;
621+
let stroke_color = Color::BLACK;
622+
623+
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
624+
responses.add(GraphOperationMessage::FillSet { layer, fill });
625+
626+
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
627+
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
628+
629+
// Create new point ids and add those into the existing vector data
630+
let mut points_map = HashMap::new();
631+
for (point, position) in new_vector.point_domain.iter() {
632+
let new_point_id = PointId::generate();
633+
points_map.insert(point, new_point_id);
634+
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
635+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
636+
}
637+
638+
// Create new segment ids and add the segments into the existing vector data
639+
let mut segments_map = HashMap::new();
640+
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
641+
let new_segment_id = SegmentId::generate();
642+
643+
segments_map.insert(segment_id, new_segment_id);
644+
645+
let handles = match bezier.handles {
646+
BezierHandles::Linear => [None, None],
647+
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
648+
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
649+
};
650+
651+
let points = [points_map[&start], points_map[&end]];
652+
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
653+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
654+
}
655+
656+
// Set G1 continuity
657+
for handles in new_vector.colinear_manipulators {
658+
let to_new_handle = |handle: HandleId| -> HandleId {
659+
HandleId {
660+
ty: handle.ty,
661+
segment: segments_map[&handle.segment],
662+
}
663+
};
664+
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
665+
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
666+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
667+
}
668+
}
669+
670+
responses.add(NodeGraphMessage::RunDocumentGraph);
671+
responses.add(Message::Defer(DeferMessage::AfterGraphRun {
672+
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
673+
}));
674+
}
675+
}
579676
PortfolioMessage::CenterPastedLayers { layers } => {
580677
if let Some(document) = self.active_document_mut() {
581678
let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]);

editor/src/messages/tool/common_functionality/shape_editor.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ pub struct SelectedLayerState {
5757
}
5858

5959
impl SelectedLayerState {
60+
pub fn is_empty(&self) -> bool {
61+
self.selected_points.is_empty() && self.selected_segments.is_empty()
62+
}
63+
6064
pub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {
6165
self.selected_points.iter().copied()
6266
}

0 commit comments

Comments
 (0)