Skip to content

Add copy/cut/paste/duplicate functionality for path geometry #2812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, molding_in_segment_edit: KeyA }),
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
Expand Down
3 changes: 3 additions & 0 deletions editor/src/messages/portfolio/portfolio_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ pub enum PortfolioMessage {
PasteSerializedData {
data: String,
},
PasteSerializedVector {
data: String,
},
CenterPastedLayers {
layers: Vec<LayerNodeIdentifier>,
},
Expand Down
91 changes: 90 additions & 1 deletion editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::document::utility_types::network_interface;
use super::spreadsheet::SpreadsheetMessageHandler;
use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid;
use crate::consts::DEFAULT_DOCUMENT_NAME;
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
use crate::messages::animation::TimingInformation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs;
Expand All @@ -12,18 +12,23 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext;
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::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*;
use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use bezier_rs::BezierHandles;
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::text::Font;
use graphene_std::vector::{HandleId, PointId, SegmentId, VectorData, VectorModificationType};
use std::vec;

#[derive(ExtractField)]
Expand Down Expand Up @@ -546,6 +551,90 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
}
}
// Custom paste implementation for path tool
PortfolioMessage::PasteSerializedVector { data } => {
// If using path tool then send the operation to path tool
if *current_tool == ToolType::Path {
responses.add(PathToolMessage::Paste { data });
}
// If not using path tool, create new layers and add paths into those
else if let Some(document) = self.active_document() {
if let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) {
let mut layers = Vec::new();
for (_layer, new_vector, transform) in data {
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
let nodes = vec![(NodeId(0), node_type.default_node_template())];

let parent = document.new_layer_parent(false);

let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
layers.push(layer);

// Adding the transform back into the layer
responses.add(GraphOperationMessage::TransformSet {
layer,
transform,
transform_in: TransformIn::Local,
skip_rerender: false,
});

// Add default fill and stroke to the layer
let fill_color = Color::WHITE;
let stroke_color = Color::BLACK;

let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
responses.add(GraphOperationMessage::FillSet { layer, fill });

let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });

// Create new point ids and add those into the existing vector data
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
let new_point_id = PointId::generate();
points_map.insert(point, new_point_id);
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}

// Create new segment ids and add the segments into the existing vector data
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
let new_segment_id = SegmentId::generate();

segments_map.insert(segment_id, new_segment_id);

let handles = match bezier.handles {
BezierHandles::Linear => [None, None],
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
};

let points = [points_map[&start], points_map[&end]];
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}

// Set G1 continuity
for handles in new_vector.colinear_manipulators {
let to_new_handle = |handle: HandleId| -> HandleId {
HandleId {
ty: handle.ty,
segment: segments_map[&handle.segment],
}
};
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}

responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CenterPastedLayers { layers });
}
}
}
PortfolioMessage::CenterPastedLayers { layers } => {
if let Some(document) = self.active_document_mut() {
let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]);
Expand Down
3 changes: 3 additions & 0 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct SelectedLayerState {
}

impl SelectedLayerState {
pub fn is_empty(&self) -> bool {
self.selected_points.is_empty() && self.selected_segments.is_empty()
}
pub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {
self.selected_points.iter().copied()
}
Expand Down
Loading
Loading