diff --git a/editor/src/consts.rs b/editor/src/consts.rs index e828e918..227cbc01 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -77,5 +77,6 @@ pub const DEFAULT_FONT_STYLE: &str = "Normal (400)"; pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.15"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite` pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const FILE_SAVE_SUFFIX: &str = ".graphite"; +pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05; diff --git a/editor/src/messages/frontend/utility_types.rs b/editor/src/messages/frontend/utility_types.rs index c2975a14..141e76b2 100644 --- a/editor/src/messages/frontend/utility_types.rs +++ b/editor/src/messages/frontend/utility_types.rs @@ -15,8 +15,8 @@ pub struct FrontendDocumentDetails { pub struct FrontendImageData { pub path: Vec, pub mime: String, - #[serde(rename = "imageData")] - pub image_data: Vec, + #[serde(skip)] + pub image_data: std::rc::Rc>, } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 2f08c6ed..4f5f0acf 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -49,9 +49,9 @@ pub struct DocumentMessageHandler { pub overlays_visible: bool, #[serde(skip)] - pub document_undo_history: Vec, + pub document_undo_history: VecDeque, #[serde(skip)] - pub document_redo_history: Vec, + pub document_redo_history: VecDeque, #[serde(with = "vectorize_layer_metadata")] pub layer_metadata: HashMap, LayerMetadata>, @@ -80,8 +80,8 @@ impl Default for DocumentMessageHandler { snapping_enabled: true, overlays_visible: true, - document_undo_history: Vec::new(), - document_redo_history: Vec::new(), + document_undo_history: VecDeque::new(), + document_redo_history: VecDeque::new(), layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(), layer_range_selection_reference: Vec::new(), @@ -508,7 +508,6 @@ impl MessageHandler { - self.backup(responses); if let Ok(_layer) = self.graphene_document.layer(&layer_path) { responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta }.into()); } @@ -541,6 +540,7 @@ impl MessageHandler) { self.document_redo_history.clear(); - self.document_undo_history.push((self.graphene_document.clone(), self.layer_metadata.clone())); + self.document_undo_history.push_back((self.graphene_document.clone(), self.layer_metadata.clone())); + if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN { + self.document_undo_history.pop_front(); + } // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); @@ -1340,7 +1343,7 @@ impl DocumentMessageHandler { let selected_paths: Vec> = self.selected_layers().map(|path| path.to_vec()).collect(); - match self.document_undo_history.pop() { + match self.document_undo_history.pop_back() { Some((document, layer_metadata)) => { // Update the currently displayed layer on the Properties panel if the selection changes after an undo action // Also appropriately update the Properties panel if an undo action results in a layer being deleted @@ -1352,7 +1355,10 @@ impl DocumentMessageHandler { let document = std::mem::replace(&mut self.graphene_document, document); let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata); - self.document_redo_history.push((document, layer_metadata)); + self.document_redo_history.push_back((document, layer_metadata)); + if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN { + self.document_redo_history.pop_front(); + } for layer in self.layer_metadata.keys() { responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()) @@ -1370,7 +1376,7 @@ impl DocumentMessageHandler { let selected_paths: Vec> = self.selected_layers().map(|path| path.to_vec()).collect(); - match self.document_redo_history.pop() { + match self.document_redo_history.pop_back() { Some((document, layer_metadata)) => { // Update currently displayed layer on property panel if selection changes after redo action // Also appropriately update property panel if redo action results in a layer being added @@ -1382,7 +1388,10 @@ impl DocumentMessageHandler { let document = std::mem::replace(&mut self.graphene_document, document); let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata); - self.document_undo_history.push((document, layer_metadata)); + self.document_undo_history.push_back((document, layer_metadata)); + if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN { + self.document_undo_history.pop_front(); + } for layer in self.layer_metadata.keys() { responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()) @@ -1398,6 +1407,7 @@ impl DocumentMessageHandler { // We can use the last state of the document to serve as the identifier to compare against // This is useful since when the document is empty the identifier will be 0 self.document_undo_history + .iter() .last() .map(|(graphene_document, _)| graphene_document.current_state_identifier()) .unwrap_or(0) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 1b6ff73d..2c2a446f 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -476,6 +476,7 @@ impl MessageHandler, mime: String, imageData: Vec, document_id: u64); + fn updateImage(path: Vec, mime: String, imageData: &[u8], document_id: u64); } /// Provides a handle to access the raw WASM memory @@ -100,7 +100,7 @@ impl JsEditorHandle { // Special case for update image data to avoid serialization times. if let FrontendMessage::UpdateImageData { document_id, image_data } = message { for image in image_data { - updateImage(image.path, image.mime, image.image_data, document_id); + updateImage(image.path, image.mime, &image.image_data, document_id); } return; } diff --git a/graphene/src/document.rs b/graphene/src/document.rs index 674298d9..a3203caf 100644 --- a/graphene/src/document.rs +++ b/graphene/src/document.rs @@ -583,6 +583,7 @@ impl Document { image_data, mime, } => { + let image_data = std::rc::Rc::new(image_data); let layer = Layer::new(LayerDataType::Image(ImageLayer::new(mime, image_data)), transform); self.set_layer(&path, layer, insert_index)?; @@ -606,6 +607,7 @@ impl Document { Operation::SetNodeGraphFrameImageData { layer_path, image_data } => { let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer"); if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data { + let image_data = std::rc::Rc::new(image_data); node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data }); } else { panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type"); @@ -831,6 +833,7 @@ impl Document { Operation::ImaginateSetImageData { layer_path, image_data } => { let layer = self.layer_mut(&layer_path).expect("Setting Imaginate image data for invalid layer"); if let LayerDataType::Imaginate(imaginate) = &mut layer.data { + let image_data = std::rc::Rc::new(image_data); imaginate.image_data = Some(ImaginateImageData { image_data }); } else { panic!("Incorrectly trying to set image data for a layer that is not an Imaginate layer type"); diff --git a/graphene/src/layers/base64_serde.rs b/graphene/src/layers/base64_serde.rs index b131822d..f755d7ae 100644 --- a/graphene/src/layers/base64_serde.rs +++ b/graphene/src/layers/base64_serde.rs @@ -2,20 +2,21 @@ use serde::{Deserialize, Deserializer, Serializer}; -pub fn as_base64(key: &T, serializer: S) -> Result +pub fn as_base64(key: &std::rc::Rc>, serializer: S) -> Result where - T: AsRef<[u8]>, S: Serializer, { - serializer.serialize_str(&base64::encode(key.as_ref())) + serializer.serialize_str(&base64::encode(key.as_slice())) } -pub fn from_base64<'a, D>(deserializer: D) -> Result, D::Error> +pub fn from_base64<'a, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'a>, { use serde::de::Error; + String::deserialize(deserializer) - .and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string()))) + .and_then(|string| base64::decode(string).map_err(|err| Error::custom(err.to_string()))) + .map(std::rc::Rc::new) .map_err(serde::de::Error::custom) } diff --git a/graphene/src/layers/image_layer.rs b/graphene/src/layers/image_layer.rs index 74d9654a..e2dca087 100644 --- a/graphene/src/layers/image_layer.rs +++ b/graphene/src/layers/image_layer.rs @@ -14,7 +14,7 @@ use std::fmt::Write; pub struct ImageLayer { pub mime: String, #[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")] - pub image_data: Vec, + pub image_data: std::rc::Rc>, // TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted) #[serde(skip)] pub blob_url: Option, @@ -75,7 +75,7 @@ impl LayerData for ImageLayer { } impl ImageLayer { - pub fn new(mime: String, image_data: Vec) -> Self { + pub fn new(mime: String, image_data: std::rc::Rc>) -> Self { Self { mime, image_data, diff --git a/graphene/src/layers/imaginate_layer.rs b/graphene/src/layers/imaginate_layer.rs index 6d55f15c..9bb68133 100644 --- a/graphene/src/layers/imaginate_layer.rs +++ b/graphene/src/layers/imaginate_layer.rs @@ -53,7 +53,7 @@ pub enum ImaginateStatus { #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct ImaginateImageData { #[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")] - pub image_data: Vec, + pub image_data: std::rc::Rc>, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] diff --git a/graphene/src/layers/nodegraph_layer.rs b/graphene/src/layers/nodegraph_layer.rs index 694eab28..41fe633a 100644 --- a/graphene/src/layers/nodegraph_layer.rs +++ b/graphene/src/layers/nodegraph_layer.rs @@ -29,7 +29,7 @@ pub struct NodeGraphFrameLayer { #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct ImageData { #[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")] - pub image_data: Vec, + pub image_data: std::rc::Rc>, } impl LayerData for NodeGraphFrameLayer {