mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 05:58:12 +08:00
Implement multiple clipboards (#425)
* Imeplement multiple Clipboards * Cargo fmt * Fix unit tests * Fix console error caused by the Pen Tool (#432) * Fix error in pen tool * Cleanup * Split make operation and remove preview Co-authored-by: Keavon Chambers <keavon@keavon.com> * Fix line centering (#431) * cargo fmt Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
0HyperCube
parent
e09a606285
commit
1eb83d92c4
@@ -96,7 +96,13 @@ impl Dispatcher {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{communication::set_uuid_seed, document::DocumentMessageHandler, message_prelude::*, misc::test_utils::EditorTestUtils, Editor};
|
use crate::{
|
||||||
|
communication::set_uuid_seed,
|
||||||
|
document::{Clipboard::*, DocumentMessageHandler},
|
||||||
|
message_prelude::*,
|
||||||
|
misc::test_utils::EditorTestUtils,
|
||||||
|
Editor,
|
||||||
|
};
|
||||||
use graphene::{color::Color, Operation};
|
use graphene::{color::Color, Operation};
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
@@ -131,8 +137,12 @@ mod test {
|
|||||||
let mut editor = create_editor_with_three_layers();
|
let mut editor = create_editor_with_three_layers();
|
||||||
|
|
||||||
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
editor.handle_message(DocumentsMessage::Copy);
|
editor.handle_message(DocumentsMessage::Copy(User));
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
|
|
||||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||||
@@ -164,8 +174,12 @@ mod test {
|
|||||||
let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1];
|
let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1];
|
||||||
|
|
||||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![shape_id]]));
|
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![shape_id]]));
|
||||||
editor.handle_message(DocumentsMessage::Copy);
|
editor.handle_message(DocumentsMessage::Copy(User));
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
|
|
||||||
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
|
|
||||||
@@ -223,10 +237,18 @@ mod test {
|
|||||||
|
|
||||||
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
|
|
||||||
editor.handle_message(DocumentsMessage::Copy);
|
editor.handle_message(DocumentsMessage::Copy(User));
|
||||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
|
|
||||||
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
|
|
||||||
@@ -284,11 +306,19 @@ mod test {
|
|||||||
let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX];
|
let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX];
|
||||||
|
|
||||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![rect_id], vec![ellipse_id]]));
|
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![rect_id], vec![ellipse_id]]));
|
||||||
editor.handle_message(DocumentsMessage::Copy);
|
editor.handle_message(DocumentsMessage::Copy(User));
|
||||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
||||||
editor.draw_rect(0., 800., 12., 200.);
|
editor.draw_rect(0., 800., 12., 200.);
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
editor.handle_message(DocumentsMessage::PasteIntoFolder { path: vec![], insert_index: -1 });
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
|
editor.handle_message(DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: User,
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
});
|
||||||
|
|
||||||
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_after_copy = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessag
|
|||||||
|
|
||||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||||
use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
|
use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
|
||||||
|
use crate::document::Clipboard;
|
||||||
use crate::input::InputPreprocessor;
|
use crate::input::InputPreprocessor;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::EditorError;
|
use crate::EditorError;
|
||||||
@@ -541,12 +542,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
let mut new_folder_path = common_prefix.to_vec();
|
let mut new_folder_path = common_prefix.to_vec();
|
||||||
new_folder_path.push(generate_uuid());
|
new_folder_path.push(generate_uuid());
|
||||||
|
|
||||||
responses.push_back(DocumentsMessage::Copy.into());
|
responses.push_back(DocumentsMessage::Copy(Clipboard::System).into());
|
||||||
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
||||||
responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into());
|
responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into());
|
||||||
responses.push_back(DocumentMessage::ToggleLayerExpansion(new_folder_path.clone()).into());
|
responses.push_back(DocumentMessage::ToggleLayerExpansion(new_folder_path.clone()).into());
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
DocumentsMessage::PasteIntoFolder {
|
DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: Clipboard::System,
|
||||||
path: new_folder_path.clone(),
|
path: new_folder_path.clone(),
|
||||||
insert_index: -1,
|
insert_index: -1,
|
||||||
}
|
}
|
||||||
@@ -792,9 +794,16 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
MoveSelectedLayersTo { path, insert_index } => {
|
MoveSelectedLayersTo { path, insert_index } => {
|
||||||
responses.push_back(DocumentsMessage::Copy.into());
|
responses.push_back(DocumentsMessage::Copy(Clipboard::System).into());
|
||||||
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
||||||
responses.push_back(DocumentsMessage::PasteIntoFolder { path, insert_index }.into());
|
responses.push_back(
|
||||||
|
DocumentsMessage::PasteIntoFolder {
|
||||||
|
clipboard: Clipboard::System,
|
||||||
|
path,
|
||||||
|
insert_index,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ReorderSelectedLayers(relative_position) => {
|
ReorderSelectedLayers(relative_position) => {
|
||||||
self.backup(responses);
|
self.backup(responses);
|
||||||
|
|||||||
@@ -10,15 +10,25 @@ use std::collections::{HashMap, VecDeque};
|
|||||||
use super::DocumentMessageHandler;
|
use super::DocumentMessageHandler;
|
||||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Clipboard {
|
||||||
|
System,
|
||||||
|
User,
|
||||||
|
_ClipboardCount,
|
||||||
|
}
|
||||||
|
static CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||||
|
|
||||||
#[impl_message(Message, Documents)]
|
#[impl_message(Message, Documents)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum DocumentsMessage {
|
pub enum DocumentsMessage {
|
||||||
Copy,
|
Copy(Clipboard),
|
||||||
PasteIntoFolder {
|
PasteIntoFolder {
|
||||||
|
clipboard: Clipboard,
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
},
|
},
|
||||||
Paste,
|
Paste(Clipboard),
|
||||||
SelectDocument(u64),
|
SelectDocument(u64),
|
||||||
CloseDocument(u64),
|
CloseDocument(u64),
|
||||||
#[child]
|
#[child]
|
||||||
@@ -49,7 +59,7 @@ pub struct DocumentsMessageHandler {
|
|||||||
documents: HashMap<u64, DocumentMessageHandler>,
|
documents: HashMap<u64, DocumentMessageHandler>,
|
||||||
document_ids: Vec<u64>,
|
document_ids: Vec<u64>,
|
||||||
active_document_id: u64,
|
active_document_id: u64,
|
||||||
copy_buffer: Vec<Layer>,
|
copy_buffer: Vec<Vec<Layer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentsMessageHandler {
|
impl DocumentsMessageHandler {
|
||||||
@@ -139,7 +149,7 @@ impl Default for DocumentsMessageHandler {
|
|||||||
Self {
|
Self {
|
||||||
documents: documents_map,
|
documents: documents_map,
|
||||||
document_ids: vec![starting_key],
|
document_ids: vec![starting_key],
|
||||||
copy_buffer: vec![],
|
copy_buffer: vec![vec![]; CLIPBOARD_COUNT as usize],
|
||||||
active_document_id: starting_key,
|
active_document_id: starting_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,19 +329,19 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
|||||||
let prev_id = self.document_ids[prev_index];
|
let prev_id = self.document_ids[prev_index];
|
||||||
responses.push_back(DocumentsMessage::SelectDocument(prev_id).into());
|
responses.push_back(DocumentsMessage::SelectDocument(prev_id).into());
|
||||||
}
|
}
|
||||||
Copy => {
|
Copy(clipboard) => {
|
||||||
let paths = self.active_document().selected_layers_sorted();
|
let paths = self.active_document().selected_layers_sorted();
|
||||||
self.copy_buffer.clear();
|
self.copy_buffer[clipboard as usize].clear();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
match self.active_document().graphene_document.layer(&path).map(|t| t.clone()) {
|
match self.active_document().graphene_document.layer(&path).map(|t| t.clone()) {
|
||||||
Ok(layer) => {
|
Ok(layer) => {
|
||||||
self.copy_buffer.push(layer);
|
self.copy_buffer[clipboard as usize].push(layer);
|
||||||
}
|
}
|
||||||
Err(e) => warn!("Could not access selected layer {:?}: {:?}", path, e),
|
Err(e) => warn!("Could not access selected layer {:?}: {:?}", path, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Paste => {
|
Paste(clipboard) => {
|
||||||
let document = self.active_document();
|
let document = self.active_document();
|
||||||
let shallowest_common_folder = document
|
let shallowest_common_folder = document
|
||||||
.graphene_document
|
.graphene_document
|
||||||
@@ -340,13 +350,14 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
|||||||
|
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
PasteIntoFolder {
|
PasteIntoFolder {
|
||||||
|
clipboard,
|
||||||
path: shallowest_common_folder.to_vec(),
|
path: shallowest_common_folder.to_vec(),
|
||||||
insert_index: -1,
|
insert_index: -1,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PasteIntoFolder { path, insert_index } => {
|
PasteIntoFolder { clipboard, path, insert_index } => {
|
||||||
let paste = |layer: &Layer, responses: &mut VecDeque<_>| {
|
let paste = |layer: &Layer, responses: &mut VecDeque<_>| {
|
||||||
log::trace!("Pasting into folder {:?} as index: {}", path, insert_index);
|
log::trace!("Pasting into folder {:?} as index: {}", path, insert_index);
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
@@ -359,11 +370,11 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
if insert_index == -1 {
|
if insert_index == -1 {
|
||||||
for layer in self.copy_buffer.iter() {
|
for layer in self.copy_buffer[clipboard as usize].iter() {
|
||||||
paste(layer, responses)
|
paste(layer, responses)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for layer in self.copy_buffer.iter().rev() {
|
for layer in self.copy_buffer[clipboard as usize].iter().rev() {
|
||||||
paste(layer, responses)
|
paste(layer, responses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub use document_file::LayerData;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_message_handler::{DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
pub use document_message_handler::{Clipboard, DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use super::{
|
|||||||
keyboard::{Key, KeyStates, NUMBER_OF_KEYS},
|
keyboard::{Key, KeyStates, NUMBER_OF_KEYS},
|
||||||
InputPreprocessor,
|
InputPreprocessor,
|
||||||
};
|
};
|
||||||
|
use crate::document::Clipboard::*;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::ToolType;
|
use crate::tool::ToolType;
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ impl Default for Mapping {
|
|||||||
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`)
|
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`)
|
||||||
let mappings = mapping![
|
let mappings = mapping![
|
||||||
// Higher priority than entries in sections below
|
// Higher priority than entries in sections below
|
||||||
entry! {action=DocumentsMessage::Paste, key_down=KeyV, modifiers=[KeyControl]},
|
entry! {action=DocumentsMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]},
|
||||||
entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift},
|
entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift},
|
||||||
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
|
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
|
||||||
// Transform layers
|
// Transform layers
|
||||||
@@ -207,7 +208,7 @@ impl Default for Mapping {
|
|||||||
// Editor Actions
|
// Editor Actions
|
||||||
entry! {action=FrontendMessage::OpenDocumentBrowse, key_down=KeyO, modifiers=[KeyControl]},
|
entry! {action=FrontendMessage::OpenDocumentBrowse, key_down=KeyO, modifiers=[KeyControl]},
|
||||||
// Document Actions
|
// Document Actions
|
||||||
entry! {action=DocumentsMessage::Paste, key_down=KeyV, modifiers=[KeyControl]},
|
entry! {action=DocumentsMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
||||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||||
@@ -252,7 +253,7 @@ impl Default for Mapping {
|
|||||||
entry! {action=DocumentsMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
entry! {action=DocumentsMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
||||||
entry! {action=DocumentsMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
entry! {action=DocumentsMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentsMessage::Copy, key_down=KeyC, modifiers=[KeyControl]},
|
entry! {action=DocumentsMessage::Copy(User), key_down=KeyC, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
||||||
// Nudging
|
// Nudging
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
||||||
|
|||||||
Reference in New Issue
Block a user