mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 08:38:12 +08:00
Desktop: Text clipboard support (#3461)
* gray background for viewport texture * cust copy paste support * connect clipboard read on web * fix eyedropper bounds * cleanup * add missing char events for some named keys like enter
This commit is contained in:
@@ -18,6 +18,7 @@ pub struct DispatcherMessageHandlers {
|
||||
animation_message_handler: AnimationMessageHandler,
|
||||
app_window_message_handler: AppWindowMessageHandler,
|
||||
broadcast_message_handler: BroadcastMessageHandler,
|
||||
clipboard_message_handler: ClipboardMessageHandler,
|
||||
debug_message_handler: DebugMessageHandler,
|
||||
defer_message_handler: DeferMessageHandler,
|
||||
dialog_message_handler: DialogMessageHandler,
|
||||
@@ -158,6 +159,7 @@ impl Dispatcher {
|
||||
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
||||
Message::Clipboard(message) => self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, ()),
|
||||
Message::Debug(message) => {
|
||||
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
@@ -319,6 +321,7 @@ impl Dispatcher {
|
||||
// TODO: Reduce the number of heap allocations
|
||||
let mut list = Vec::new();
|
||||
list.extend(self.message_handlers.app_window_message_handler.actions());
|
||||
list.extend(self.message_handlers.clipboard_message_handler.actions());
|
||||
list.extend(self.message_handlers.dialog_message_handler.actions());
|
||||
list.extend(self.message_handlers.animation_message_handler.actions());
|
||||
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, Clipboard)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ClipboardMessage {
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
ReadClipboard { content: ClipboardContentRaw },
|
||||
ReadSelection { content: Option<String>, cut: bool },
|
||||
Write { content: ClipboardContent },
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::raster::Image;
|
||||
use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||
|
||||
const CLIPBOARD_PREFIX_LAYER: &str = "graphite/layer: ";
|
||||
const CLIPBOARD_PREFIX_NODES: &str = "graphite/nodes: ";
|
||||
const CLIPBOARD_PREFIX_VECTOR: &str = "graphite/vector: ";
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct ClipboardMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ClipboardMessage, ()> for ClipboardMessageHandler {
|
||||
fn process_message(&mut self, message: ClipboardMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||
match message {
|
||||
ClipboardMessage::Cut => responses.add(FrontendMessage::TriggerSelectionRead { cut: true }),
|
||||
ClipboardMessage::Copy => responses.add(FrontendMessage::TriggerSelectionRead { cut: false }),
|
||||
ClipboardMessage::Paste => responses.add(FrontendMessage::TriggerClipboardRead),
|
||||
ClipboardMessage::ReadClipboard { content } => match content {
|
||||
ClipboardContentRaw::Text(text) => {
|
||||
if let Some(layer) = text.strip_prefix(CLIPBOARD_PREFIX_LAYER) {
|
||||
responses.add(PortfolioMessage::PasteSerializedData { data: layer.to_string() });
|
||||
} else if let Some(nodes) = text.strip_prefix(CLIPBOARD_PREFIX_NODES) {
|
||||
responses.add(NodeGraphMessage::PasteNodes { serialized_nodes: nodes.to_string() });
|
||||
} else if let Some(vector) = text.strip_prefix(CLIPBOARD_PREFIX_VECTOR) {
|
||||
responses.add(PortfolioMessage::PasteSerializedVector { data: vector.to_string() });
|
||||
} else {
|
||||
responses.add(FrontendMessage::TriggerSelectionWrite { content: text });
|
||||
}
|
||||
}
|
||||
ClipboardContentRaw::Svg(svg) => {
|
||||
responses.add(PortfolioMessage::PasteSvg {
|
||||
svg,
|
||||
name: None,
|
||||
mouse: None,
|
||||
parent_and_insert_index: None,
|
||||
});
|
||||
}
|
||||
ClipboardContentRaw::Image { data, width, height } => {
|
||||
responses.add(PortfolioMessage::PasteImage {
|
||||
image: Image::from_image_data(&data, width, height),
|
||||
name: None,
|
||||
mouse: None,
|
||||
parent_and_insert_index: None,
|
||||
});
|
||||
}
|
||||
},
|
||||
ClipboardMessage::ReadSelection { content, cut } => {
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
if let Some(text) = content {
|
||||
responses.add(ClipboardMessage::Write {
|
||||
content: ClipboardContent::Text(text),
|
||||
});
|
||||
} else if cut {
|
||||
responses.add(PortfolioMessage::Cut { clipboard: Clipboard::Device });
|
||||
} else {
|
||||
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Device });
|
||||
}
|
||||
}
|
||||
ClipboardMessage::Write { content } => {
|
||||
let text = match content {
|
||||
ClipboardContent::Svg(_) => {
|
||||
log::error!("SVG copying is not yet supported");
|
||||
return;
|
||||
}
|
||||
ClipboardContent::Image { .. } => {
|
||||
log::error!("Image copying is not yet supported");
|
||||
return;
|
||||
}
|
||||
ClipboardContent::Layer(layer) => format!("{CLIPBOARD_PREFIX_LAYER}{layer}"),
|
||||
ClipboardContent::Nodes(nodes) => format!("{CLIPBOARD_PREFIX_NODES}{nodes}"),
|
||||
ClipboardContent::Vector(vector) => format!("{CLIPBOARD_PREFIX_VECTOR}{vector}"),
|
||||
ClipboardContent::Text(text) => text,
|
||||
};
|
||||
responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
|
||||
}
|
||||
}
|
||||
}
|
||||
advertise_actions!(ClipboardMessageDiscriminant;
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
mod clipboard_message;
|
||||
pub mod clipboard_message_handler;
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use clipboard_message::{ClipboardMessage, ClipboardMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use clipboard_message_handler::ClipboardMessageHandler;
|
||||
@@ -0,0 +1,16 @@
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ClipboardContentRaw {
|
||||
Text(String),
|
||||
Svg(String),
|
||||
Image { data: Vec<u8>, width: u32, height: u32 },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ClipboardContent {
|
||||
Layer(String),
|
||||
Nodes(String),
|
||||
Vector(String),
|
||||
Text(String),
|
||||
Svg(String),
|
||||
Image { data: Vec<u8>, width: u32, height: u32 },
|
||||
}
|
||||
@@ -66,7 +66,7 @@ pub enum FrontendMessage {
|
||||
shortcut: Option<ActionShortcut>,
|
||||
},
|
||||
|
||||
// Trigger prefix: cause a browser API to do something
|
||||
// Trigger prefix: cause a frontend specific API to do something
|
||||
TriggerAboutGraphiteLocalizedCommitDate {
|
||||
#[serde(rename = "commitDate")]
|
||||
commit_date: String,
|
||||
@@ -111,7 +111,6 @@ pub enum FrontendMessage {
|
||||
TriggerOpenLaunchDocuments,
|
||||
TriggerLoadPreferences,
|
||||
TriggerOpenDocument,
|
||||
TriggerPaste,
|
||||
TriggerSavePreferences {
|
||||
preferences: PreferencesMessageHandler,
|
||||
},
|
||||
@@ -120,13 +119,19 @@ pub enum FrontendMessage {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
TriggerTextCommit,
|
||||
TriggerTextCopy {
|
||||
#[serde(rename = "copyText")]
|
||||
copy_text: String,
|
||||
},
|
||||
TriggerVisitLink {
|
||||
url: String,
|
||||
},
|
||||
TriggerClipboardRead,
|
||||
TriggerClipboardWrite {
|
||||
content: String,
|
||||
},
|
||||
TriggerSelectionRead {
|
||||
cut: bool,
|
||||
},
|
||||
TriggerSelectionWrite {
|
||||
content: String,
|
||||
},
|
||||
|
||||
// Update prefix: give the frontend a new value or state for it to use
|
||||
UpdateActiveDocument {
|
||||
@@ -330,12 +335,15 @@ pub enum FrontendMessage {
|
||||
width: f64,
|
||||
height: f64,
|
||||
},
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
RenderOverlays {
|
||||
#[serde(skip, default = "OverlayContext::default")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
context: OverlayContext,
|
||||
},
|
||||
|
||||
// Window prefix: cause the application window to do something
|
||||
WindowClose,
|
||||
WindowMinimize,
|
||||
WindowMaximize,
|
||||
|
||||
@@ -54,6 +54,11 @@ pub fn input_mappings() -> Mapping {
|
||||
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
||||
//
|
||||
// ClipboardMessage
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),
|
||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=ClipboardMessage::Paste),
|
||||
//
|
||||
// NodeGraphMessage
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
|
||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
|
||||
@@ -433,9 +438,6 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
||||
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
||||
//
|
||||
// FrontendMessage
|
||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
||||
//
|
||||
// DialogMessage
|
||||
entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
@@ -196,20 +195,20 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
MenuListEntry::new("Cut")
|
||||
.label("Cut")
|
||||
.icon("Cut")
|
||||
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Cut))
|
||||
.on_commit(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into())
|
||||
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Cut))
|
||||
.on_commit(|_| ClipboardMessage::Cut.into())
|
||||
.disabled(no_active_document || !has_selected_layers),
|
||||
MenuListEntry::new("Copy")
|
||||
.label("Copy")
|
||||
.icon("Copy")
|
||||
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Copy))
|
||||
.on_commit(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into())
|
||||
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Copy))
|
||||
.on_commit(|_| ClipboardMessage::Copy.into())
|
||||
.disabled(no_active_document || !has_selected_layers),
|
||||
MenuListEntry::new("Paste")
|
||||
.label("Paste")
|
||||
.icon("Paste")
|
||||
.tooltip_shortcut(action_shortcut!(FrontendMessageDiscriminant::TriggerPaste))
|
||||
.on_commit(|_| FrontendMessage::TriggerPaste.into())
|
||||
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Paste))
|
||||
.on_commit(|_| ClipboardMessage::Paste.into())
|
||||
.disabled(no_active_document),
|
||||
],
|
||||
vec![
|
||||
|
||||
@@ -12,6 +12,8 @@ pub enum Message {
|
||||
#[child]
|
||||
Broadcast(BroadcastMessage),
|
||||
#[child]
|
||||
Clipboard(ClipboardMessage),
|
||||
#[child]
|
||||
Debug(DebugMessage),
|
||||
#[child]
|
||||
Defer(DeferMessage),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
pub mod animation;
|
||||
pub mod app_window;
|
||||
pub mod broadcast;
|
||||
pub mod clipboard;
|
||||
pub mod debug;
|
||||
pub mod defer;
|
||||
pub mod dialog;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
|
||||
@@ -237,11 +238,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
let new_ids = &all_selected_nodes.iter().enumerate().map(|(new, old)| (*old, NodeId(new as u64))).collect();
|
||||
let copied_nodes = network_interface.copy_nodes(new_ids, selection_network_path).collect::<Vec<_>>();
|
||||
|
||||
// Prefix to show that these are nodes
|
||||
let mut copy_text = String::from("graphite/nodes: ");
|
||||
copy_text += &serde_json::to_string(&copied_nodes).expect("Could not serialize copy");
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
||||
let Ok(data) = serde_json::to_string(&copied_nodes) else {
|
||||
log::error!("Failed to serialize nodes for clipboard");
|
||||
return;
|
||||
};
|
||||
responses.add(ClipboardMessage::Write {
|
||||
content: ClipboardContent::Nodes(data),
|
||||
});
|
||||
}
|
||||
NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer } => {
|
||||
let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) else {
|
||||
|
||||
@@ -5,10 +5,9 @@ use graph_craft::document::NodeId;
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
|
||||
pub enum Clipboard {
|
||||
Internal,
|
||||
Device,
|
||||
|
||||
_InternalClipboardCount, // Keep this as the last entry of **internal** clipboards since it is used for counting the number of enum variants
|
||||
|
||||
Device,
|
||||
}
|
||||
|
||||
pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;
|
||||
|
||||
@@ -6469,7 +6469,7 @@ mod network_interface_tests {
|
||||
let serialized_nodes = frontend_messages
|
||||
.into_iter()
|
||||
.find_map(|msg| match msg {
|
||||
FrontendMessage::TriggerTextCopy { copy_text } => Some(copy_text),
|
||||
FrontendMessage::TriggerClipboardWrite { content } => Some(content),
|
||||
_ => None,
|
||||
})
|
||||
.expect("copy message should be dispatched")
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::utility_types::{PanelType, PersistentData};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||
use crate::messages::dialog::simple_dialogs;
|
||||
use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
@@ -243,11 +244,21 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::Copy { clipboard } => {
|
||||
if context.current_tool == &ToolType::Path {
|
||||
responses.add(PathToolMessage::Copy { clipboard });
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
||||
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if active_document.graph_view_overlay_open() {
|
||||
responses.add(NodeGraphMessage::Copy);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
||||
let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
|
||||
|
||||
@@ -283,10 +294,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
if clipboard == Clipboard::Device {
|
||||
let mut buffer = Vec::new();
|
||||
copy_val(&mut buffer);
|
||||
let mut copy_text = String::from("graphite/layer: ");
|
||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
||||
let Ok(data) = serde_json::to_string(&buffer) else {
|
||||
log::error!("Failed to serialize nodes for clipboard");
|
||||
return;
|
||||
};
|
||||
responses.add(ClipboardMessage::Write {
|
||||
content: ClipboardContent::Layer(data),
|
||||
});
|
||||
} else {
|
||||
let copy_buffer = &mut self.copy_buffer;
|
||||
copy_buffer[clipboard as usize].clear();
|
||||
@@ -294,6 +308,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::Cut { clipboard } => {
|
||||
if context.current_tool == &ToolType::Path {
|
||||
responses.add(PathToolMessage::Cut { clipboard });
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(active_document) = self.active_document()
|
||||
&& active_document.graph_view_overlay_open()
|
||||
{
|
||||
responses.add(NodeGraphMessage::Copy);
|
||||
return;
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::Copy { clipboard });
|
||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscrimin
|
||||
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
|
||||
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
|
||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||
pub use crate::messages::clipboard::{ClipboardMessage, ClipboardMessageDiscriminant, ClipboardMessageHandler};
|
||||
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
||||
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
|
||||
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Fsm for EyedropperToolFsmState {
|
||||
// Sampling -> Sampling
|
||||
(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
|
||||
let mouse_position = viewport.logical(input.mouse.position);
|
||||
if viewport.is_in_bounds(mouse_position) {
|
||||
if viewport.is_in_bounds(mouse_position + viewport.offset()) {
|
||||
update_cursor_preview(responses, input, global_tool_data, None);
|
||||
} else {
|
||||
disable_cursor_preview(responses);
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD,
|
||||
DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
};
|
||||
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_shortcut_manual;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
@@ -2732,10 +2733,13 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
|
||||
if clipboard == Clipboard::Device {
|
||||
let mut copy_text = String::from("graphite/vector: ");
|
||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
||||
|
||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
||||
if let Ok(data) = serde_json::to_string(&buffer) {
|
||||
responses.add(ClipboardMessage::Write {
|
||||
content: ClipboardContent::Vector(data),
|
||||
});
|
||||
} else {
|
||||
log::error!("Failed to serialize nodes for clipboard");
|
||||
}
|
||||
}
|
||||
// TODO: Add implementation for internal clipboard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user