Skip to content

Commit 51c31f0

Browse files
0HyperCubeKeavon
andcommitted
Add embedable images (#564)
* Add embedable bitmaps * Initial work on blob urls * Finish implementing data url * Fix some bugs * Rename bitmap to image * Fix loading image on document load * Add transform properties for image * Remove some logging * Add image dimensions * Implement system copy and paste * Fix pasting images * Fix test * Address code review Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 0ee492a commit 51c31f0

File tree

23 files changed

+462
-59
lines changed

23 files changed

+462
-59
lines changed

editor/src/communication/dispatcher.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ impl Dispatcher {
6464
#[remain::unsorted]
6565
NoOp => {}
6666
Frontend(message) => {
67+
// Image data should be immediatly handled
68+
if let FrontendMessage::UpdateImageData { .. } = message {
69+
self.responses.push(message);
70+
return;
71+
}
72+
6773
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
6874
self.responses.push(message);
6975
}
@@ -169,9 +175,9 @@ mod test {
169175
let mut editor = create_editor_with_three_layers();
170176

171177
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
172-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::User });
178+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
173179
editor.handle_message(PortfolioMessage::PasteIntoFolder {
174-
clipboard: Clipboard::User,
180+
clipboard: Clipboard::Internal,
175181
folder_path: vec![],
176182
insert_index: -1,
177183
});
@@ -208,9 +214,9 @@ mod test {
208214
editor.handle_message(DocumentMessage::SetSelectedLayers {
209215
replacement_selected_layers: vec![vec![shape_id]],
210216
});
211-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::User });
217+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
212218
editor.handle_message(PortfolioMessage::PasteIntoFolder {
213-
clipboard: Clipboard::User,
219+
clipboard: Clipboard::Internal,
214220
folder_path: vec![],
215221
insert_index: -1,
216222
});
@@ -273,15 +279,15 @@ mod test {
273279

274280
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
275281

276-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::User });
282+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
277283
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
278284
editor.handle_message(PortfolioMessage::PasteIntoFolder {
279-
clipboard: Clipboard::User,
285+
clipboard: Clipboard::Internal,
280286
folder_path: vec![],
281287
insert_index: -1,
282288
});
283289
editor.handle_message(PortfolioMessage::PasteIntoFolder {
284-
clipboard: Clipboard::User,
290+
clipboard: Clipboard::Internal,
285291
folder_path: vec![],
286292
insert_index: -1,
287293
});
@@ -344,16 +350,16 @@ mod test {
344350
editor.handle_message(DocumentMessage::SetSelectedLayers {
345351
replacement_selected_layers: vec![vec![rect_id], vec![ellipse_id]],
346352
});
347-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::User });
353+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
348354
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
349355
editor.draw_rect(0., 800., 12., 200.);
350356
editor.handle_message(PortfolioMessage::PasteIntoFolder {
351-
clipboard: Clipboard::User,
357+
clipboard: Clipboard::Internal,
352358
folder_path: vec![],
353359
insert_index: -1,
354360
});
355361
editor.handle_message(PortfolioMessage::PasteIntoFolder {
356-
clipboard: Clipboard::User,
362+
clipboard: Clipboard::Internal,
357363
folder_path: vec![],
358364
insert_index: -1,
359365
});

editor/src/document/clipboards.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use serde::{Deserialize, Serialize};
77
#[repr(u8)]
88
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
99
pub enum Clipboard {
10-
System,
11-
User,
12-
_ClipboardCount, // Keep this as the last entry since it is used for counting the number of enum variants
10+
Internal,
11+
12+
_InternalClipboardCount, // Keep this as the last entry in internal clipboards since it is used for counting the number of enum variants
13+
14+
Device,
1315
}
1416

15-
pub const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
17+
pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;
1618

1719
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1820
pub struct CopyBufferEntry {

editor/src/document/document_message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ pub enum DocumentMessage {
7979
delta_x: f64,
8080
delta_y: f64,
8181
},
82+
PasteImage {
83+
mime: String,
84+
image_data: Vec<u8>,
85+
mouse: Option<(f64, f64)>,
86+
},
8287
Redo,
8388
RenameLayer {
8489
layer_path: Vec<LayerId>,

editor/src/document/document_message_handler.rs

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandl
66
use crate::consts::{
77
ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
88
};
9+
use crate::frontend::utility_types::FrontendImageData;
910
use crate::input::InputPreprocessorMessageHandler;
1011
use crate::layout::widgets::{
1112
IconButton, LayoutRow, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, PropertyHolder, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, Widget,
@@ -470,6 +471,33 @@ impl DocumentMessageHandler {
470471
path.push(generate_uuid());
471472
path
472473
}
474+
475+
/// Creates the blob URLs for the image data in the document
476+
pub fn load_image_data(&self, responses: &mut VecDeque<Message>, root: &LayerDataType, mut path: Vec<LayerId>) {
477+
let mut image_data = Vec::new();
478+
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, responses: &mut VecDeque<Message>, image_data: &mut Vec<FrontendImageData>) {
479+
match data {
480+
LayerDataType::Folder(f) => {
481+
for (id, layer) in f.layer_ids.iter().zip(f.layers().iter()) {
482+
path.push(*id);
483+
walk_layers(&layer.data, path, responses, image_data);
484+
path.pop();
485+
}
486+
}
487+
LayerDataType::Image(img) => image_data.push(FrontendImageData {
488+
path: path.clone(),
489+
image_data: img.image_data.clone(),
490+
mime: img.mime.clone(),
491+
}),
492+
_ => {}
493+
}
494+
}
495+
496+
walk_layers(root, &mut path, responses, &mut image_data);
497+
if !image_data.is_empty() {
498+
responses.push_front(FrontendMessage::UpdateImageData { image_data }.into());
499+
}
500+
}
473501
}
474502

475503
impl PropertyHolder for DocumentMessageHandler {
@@ -783,7 +811,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
783811
}
784812
DirtyRenderDocument => {
785813
// Mark all non-overlay caches as dirty
786-
GrapheneDocument::visit_all_shapes(&mut self.graphene_document.root, &mut |_| {});
814+
GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root);
787815

788816
responses.push_back(DocumentMessage::RenderDocument.into());
789817
}
@@ -865,13 +893,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
865893

866894
new_folder_path.push(generate_uuid());
867895

868-
responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::System }.into());
896+
responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into());
869897
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
870898
responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into());
871899
responses.push_back(DocumentMessage::ToggleLayerExpansion { layer_path: new_folder_path.clone() }.into());
872900
responses.push_back(
873901
PortfolioMessage::PasteIntoFolder {
874-
clipboard: Clipboard::System,
902+
clipboard: Clipboard::Internal,
875903
folder_path: new_folder_path.clone(),
876904
insert_index: -1,
877905
}
@@ -904,11 +932,11 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
904932

905933
let insert_index = self.update_insert_index(&selected_layers, &folder_path, insert_index, reverse_index).unwrap();
906934

907-
responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::System }.into());
935+
responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into());
908936
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
909937
responses.push_back(
910938
PortfolioMessage::PasteIntoFolder {
911-
clipboard: Clipboard::System,
939+
clipboard: Clipboard::Internal,
912940
folder_path,
913941
insert_index,
914942
}
@@ -926,6 +954,39 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
926954
}
927955
responses.push_back(ToolMessage::DocumentIsDirty.into());
928956
}
957+
PasteImage { mime, image_data, mouse } => {
958+
let path = vec![generate_uuid()];
959+
responses.push_front(
960+
FrontendMessage::UpdateImageData {
961+
image_data: vec![FrontendImageData {
962+
path: path.clone(),
963+
image_data: image_data.clone(),
964+
mime: mime.clone(),
965+
}],
966+
}
967+
.into(),
968+
);
969+
responses.push_back(
970+
DocumentOperation::AddImage {
971+
path: path.clone(),
972+
transform: DAffine2::ZERO.to_cols_array(),
973+
insert_index: -1,
974+
mime,
975+
image_data,
976+
}
977+
.into(),
978+
);
979+
responses.push_back(
980+
DocumentMessage::SetSelectedLayers {
981+
replacement_selected_layers: vec![path.clone()],
982+
}
983+
.into(),
984+
);
985+
986+
let mouse = mouse.map_or(ipp.mouse.position, |pos| pos.into());
987+
let transform = DAffine2::from_translation(mouse - ipp.viewport_bounds.top_left).to_cols_array();
988+
responses.push_back(DocumentOperation::SetLayerTransformInViewport { path, transform }.into());
989+
}
929990
Redo => {
930991
responses.push_back(SelectToolMessage::Abort.into());
931992
responses.push_back(DocumentHistoryForward.into());
@@ -1200,10 +1261,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
12001261
// Select them
12011262
DocumentMessage::SetSelectedLayers { replacement_selected_layers: select }.into(),
12021263
// Copy them
1203-
PortfolioMessage::Copy { clipboard: Clipboard::System }.into(),
1264+
PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into(),
12041265
// Paste them into the folder above
12051266
PortfolioMessage::PasteIntoFolder {
1206-
clipboard: Clipboard::System,
1267+
clipboard: Clipboard::Internal,
12071268
folder_path: folder_path[..folder_path.len() - 1].to_vec(),
12081269
insert_index: -1,
12091270
}

editor/src/document/layer_panel.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub enum LayerDataTypeDiscriminant {
100100
Folder,
101101
Shape,
102102
Text,
103+
Image,
103104
}
104105

105106
impl fmt::Display for LayerDataTypeDiscriminant {
@@ -108,6 +109,7 @@ impl fmt::Display for LayerDataTypeDiscriminant {
108109
LayerDataTypeDiscriminant::Folder => "Folder",
109110
LayerDataTypeDiscriminant::Shape => "Shape",
110111
LayerDataTypeDiscriminant::Text => "Text",
112+
LayerDataTypeDiscriminant::Image => "Image",
111113
};
112114

113115
formatter.write_str(name)
@@ -122,6 +124,7 @@ impl From<&LayerDataType> for LayerDataTypeDiscriminant {
122124
Folder(_) => LayerDataTypeDiscriminant::Folder,
123125
Shape(_) => LayerDataTypeDiscriminant::Shape,
124126
Text(_) => LayerDataTypeDiscriminant::Text,
127+
Image(_) => LayerDataTypeDiscriminant::Image,
125128
}
126129
}
127130
}

editor/src/document/portfolio_message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ pub enum PortfolioMessage {
5555
folder_path: Vec<LayerId>,
5656
insert_index: isize,
5757
},
58+
PasteSerializedData {
59+
data: String,
60+
},
5861
PrevDocument,
5962
RequestAboutGraphiteDialog,
6063
SelectDocument {

editor/src/document/portfolio_message_handler.rs

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::clipboards::{CopyBufferEntry, CLIPBOARD_COUNT};
1+
use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
22
use super::DocumentMessageHandler;
33
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
44
use crate::frontend::utility_types::FrontendDocumentDetails;
@@ -17,7 +17,7 @@ pub struct PortfolioMessageHandler {
1717
documents: HashMap<u64, DocumentMessageHandler>,
1818
document_ids: Vec<u64>,
1919
active_document_id: u64,
20-
copy_buffer: [Vec<CopyBufferEntry>; CLIPBOARD_COUNT as usize],
20+
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
2121
}
2222

2323
impl PortfolioMessageHandler {
@@ -78,6 +78,8 @@ impl PortfolioMessageHandler {
7878
.collect::<Vec<_>>(),
7979
);
8080

81+
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
82+
8183
self.documents.insert(document_id, new_document);
8284

8385
// Send the new list of document tab names
@@ -119,7 +121,7 @@ impl Default for PortfolioMessageHandler {
119121
Self {
120122
documents: documents_map,
121123
document_ids: vec![starting_key],
122-
copy_buffer: [EMPTY_VEC; CLIPBOARD_COUNT as usize],
124+
copy_buffer: [EMPTY_VEC; INTERNAL_CLIPBOARD_COUNT as usize],
123125
active_document_id: starting_key,
124126
}
125127
}
@@ -228,16 +230,28 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
228230
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
229231
let active_document = self.documents.get(&self.active_document_id).unwrap();
230232

231-
let copy_buffer = &mut self.copy_buffer;
232-
copy_buffer[clipboard as usize].clear();
233-
234-
for layer_path in active_document.selected_layers_without_children() {
235-
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
236-
(Ok(layer), layer_metadata) => {
237-
copy_buffer[clipboard as usize].push(CopyBufferEntry { layer, layer_metadata });
233+
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
234+
for layer_path in active_document.selected_layers_without_children() {
235+
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
236+
(Ok(layer), layer_metadata) => {
237+
buffer.push(CopyBufferEntry { layer, layer_metadata });
238+
}
239+
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
238240
}
239-
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
240241
}
242+
};
243+
244+
if clipboard == Clipboard::Device {
245+
let mut buffer = Vec::new();
246+
copy_val(&mut buffer);
247+
let mut copy_text = String::from("graphite/layer: ");
248+
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
249+
250+
responses.push_back(FrontendMessage::TriggerTextCopy { copy_text }.into());
251+
} else {
252+
let copy_buffer = &mut self.copy_buffer;
253+
copy_buffer[clipboard as usize].clear();
254+
copy_val(&mut copy_buffer[clipboard as usize]);
241255
}
242256
}
243257
Cut { clipboard } => {
@@ -331,6 +345,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
331345
}
332346
.into(),
333347
);
348+
self.active_document().load_image_data(responses, &entry.layer.data, destination_path.clone());
334349
responses.push_front(
335350
DocumentOperation::InsertLayer {
336351
layer: entry.layer.clone(),
@@ -351,6 +366,40 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
351366
}
352367
}
353368
}
369+
PasteSerializedData { data } => {
370+
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
371+
let document = self.active_document();
372+
let shallowest_common_folder = document
373+
.graphene_document
374+
.shallowest_common_folder(document.selected_layers())
375+
.expect("While pasting from serialized, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
376+
responses.push_back(DeselectAllLayers.into());
377+
responses.push_back(StartTransaction.into());
378+
379+
for entry in data {
380+
let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat();
381+
382+
responses.push_front(
383+
DocumentMessage::UpdateLayerMetadata {
384+
layer_path: destination_path.clone(),
385+
layer_metadata: entry.layer_metadata,
386+
}
387+
.into(),
388+
);
389+
self.active_document().load_image_data(responses, &entry.layer.data, destination_path.clone());
390+
responses.push_front(
391+
DocumentOperation::InsertLayer {
392+
layer: entry.layer.clone(),
393+
destination_path,
394+
insert_index: -1,
395+
}
396+
.into(),
397+
);
398+
}
399+
400+
responses.push_back(CommitTransaction.into());
401+
}
402+
}
354403
PrevDocument => {
355404
let len = self.document_ids.len();
356405
let current_index = self.document_index(self.active_document_id);

0 commit comments

Comments
 (0)