diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 2a76e1ccfe..dd414e6e72 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -43,3 +43,6 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg"; // COLORS pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.); + +// Document +pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.1"; diff --git a/editor/src/document/artboard_message_handler.rs b/editor/src/document/artboard_message_handler.rs new file mode 100644 index 0000000000..4046a33c04 --- /dev/null +++ b/editor/src/document/artboard_message_handler.rs @@ -0,0 +1,83 @@ +pub use crate::document::layer_panel::*; +use crate::document::{DocumentMessage, LayerMetadata}; +use crate::input::InputPreprocessor; +use crate::message_prelude::*; +use glam::{DAffine2, DVec2}; +use graphene::color::Color; +use graphene::document::Document as GrapheneDocument; +use graphene::layers::style::{self, Fill, ViewMode}; +use graphene::Operation as DocumentOperation; +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; + +#[impl_message(Message, DocumentMessage, Artboard)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum ArtboardMessage { + DispatchOperation(Box), + AddArtboard { top: f64, left: f64, height: f64, width: f64 }, + RenderArtboards, +} + +impl From for ArtboardMessage { + fn from(operation: DocumentOperation) -> Self { + Self::DispatchOperation(Box::new(operation)) + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ArtboardMessageHandler { + pub artboards_graphene_document: GrapheneDocument, + pub artboard_ids: Vec, +} + +impl MessageHandler for ArtboardMessageHandler { + fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque) { + // let (layer_metadata, document, ipp) = data; + use ArtboardMessage::*; + match message { + DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(&operation) { + Ok(_) => (), + Err(e) => log::error!("Artboard Error: {:?}", e), + }, + AddArtboard { top, left, height, width } => { + let artboard_id = generate_uuid(); + self.artboard_ids.push(artboard_id); + + responses.push_back( + ArtboardMessage::DispatchOperation( + DocumentOperation::AddRect { + path: vec![artboard_id], + insert_index: -1, + transform: DAffine2::from_scale_angle_translation(DVec2::new(height, width), 0., DVec2::new(top, left)).to_cols_array(), + style: style::PathStyle::new(None, Some(Fill::new(Color::WHITE))), + } + .into(), + ) + .into(), + ); + } + RenderArtboards => {} + } + + // Render an infinite canvas if there are no artboards + if self.artboard_ids.is_empty() { + responses.push_back( + FrontendMessage::UpdateArtboards { + svg: r##""##.to_string(), + } + .into(), + ) + } else { + responses.push_back( + FrontendMessage::UpdateArtboards { + svg: self.artboards_graphene_document.render_root(ViewMode::Normal), + } + .into(), + ); + } + } + + fn actions(&self) -> ActionList { + actions!(ArtBoardMessageDiscriminant;) + } +} diff --git a/editor/src/document/document_file.rs b/editor/src/document/document_file.rs index 65992efd2b..a3405fe5b3 100644 --- a/editor/src/document/document_file.rs +++ b/editor/src/document/document_file.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::collections::VecDeque; +use super::artboard_message_handler::ArtboardMessage; +use super::artboard_message_handler::ArtboardMessageHandler; pub use super::layer_panel::*; use super::movement_handler::{MovementMessage, MovementMessageHandler}; use super::overlay_message_handler::OverlayMessageHandler; @@ -8,6 +10,7 @@ use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessag use super::vectorize_layer_metadata; use crate::consts::DEFAULT_DOCUMENT_NAME; +use crate::consts::GRAPHITE_DOCUMENT_VERSION; use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING}; use crate::document::Clipboard; use crate::input::InputPreprocessor; @@ -83,10 +86,12 @@ pub struct DocumentMessageHandler { movement_handler: MovementMessageHandler, #[serde(skip)] overlay_message_handler: OverlayMessageHandler, + artboard_message_handler: ArtboardMessageHandler, #[serde(skip)] transform_layer_handler: TransformLayerMessageHandler, pub snapping_enabled: bool, pub view_mode: ViewMode, + pub version: String, } impl Default for DocumentMessageHandler { @@ -101,9 +106,11 @@ impl Default for DocumentMessageHandler { layer_range_selection_reference: Vec::new(), movement_handler: MovementMessageHandler::default(), overlay_message_handler: OverlayMessageHandler::default(), + artboard_message_handler: ArtboardMessageHandler::default(), transform_layer_handler: TransformLayerMessageHandler::default(), snapping_enabled: true, view_mode: ViewMode::default(), + version: GRAPHITE_DOCUMENT_VERSION.to_string(), } } } @@ -118,6 +125,8 @@ pub enum DocumentMessage { DispatchOperation(Box), #[child] Overlay(OverlayMessage), + #[child] + Artboard(ArtboardMessage), UpdateLayerMetadata { layer_path: Vec, layer_metadata: LayerMetadata, @@ -195,20 +204,34 @@ impl DocumentMessageHandler { } pub fn deserialize_document(serialized_content: &str) -> Result { - log::info!("Deserializing: {:?}", serialized_content); - serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string())) + let deserialized_result: Result = serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string())); + match deserialized_result { + Ok(document) => { + if document.version != GRAPHITE_DOCUMENT_VERSION { + Err(DocumentError::InvalidFile("Graphite document version mismatch".to_string())) + } else { + Ok(document) + } + } + Err(e) => Err(e), + } } pub fn with_name(name: String, ipp: &InputPreprocessor) -> Self { let mut document = Self { name, ..Self::default() }; - document.graphene_document.root.transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); + let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); + document.graphene_document.root.transform = starting_root_transform; + document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform; document } - pub fn with_name_and_content(name: String, serialized_content: String) -> Result { + pub fn with_name_and_content(name: String, serialized_content: String, ipp: &InputPreprocessor) -> Result { match Self::deserialize_document(&serialized_content) { Ok(mut document) => { document.name = name; + let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); + document.graphene_document.root.transform = starting_root_transform; + document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform; Ok(document) } Err(DocumentError::InvalidFile(msg)) => Err(EditorError::Document(msg)), @@ -546,6 +569,13 @@ impl MessageHandler for DocumentMessageHand ); // responses.push_back(OverlayMessage::RenderOverlays.into()); } + Artboard(message) => { + self.artboard_message_handler.process_action( + message, + (Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp), + responses, + ); + } ExportDocument => { let bbox = self.graphene_document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]); let size = bbox[1] - bbox[0]; @@ -827,6 +857,8 @@ impl MessageHandler for DocumentMessageHand } .into(), ); + responses.push_back(ArtboardMessage::RenderArtboards.into()); + let document_transform = &self.movement_handler; let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform.scale * SCALE_EFFECT; diff --git a/editor/src/document/document_message_handler.rs b/editor/src/document/document_message_handler.rs index 2c912a7b05..ee000cfdfd 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/document/document_message_handler.rs @@ -1,5 +1,5 @@ use super::{DocumentMessageHandler, LayerMetadata}; -use crate::consts::DEFAULT_DOCUMENT_NAME; +use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::frontend::frontend_message_handler::FrontendDocumentDetails; use crate::input::InputPreprocessor; use crate::message_prelude::*; @@ -196,6 +196,7 @@ impl MessageHandler for DocumentsMessageHa for layer in self.active_document().layer_metadata.keys() { responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into()); } + responses.push_back(ToolMessage::DocumentIsDirty.into()); } CloseActiveDocumentWithConfirmation => { responses.push_back(DocumentsMessage::CloseDocumentWithConfirmation(self.active_document_id).into()); @@ -293,7 +294,7 @@ impl MessageHandler for DocumentsMessageHa document, document_is_saved, } => { - let document = DocumentMessageHandler::with_name_and_content(document_name, document); + let document = DocumentMessageHandler::with_name_and_content(document_name, document, ipp); match document { Ok(mut document) => { document.set_save_state(document_is_saved); @@ -333,6 +334,7 @@ impl MessageHandler for DocumentsMessageHa id, name: document.name.clone(), }, + version: GRAPHITE_DOCUMENT_VERSION.to_string(), } .into(), ) diff --git a/editor/src/document/mod.rs b/editor/src/document/mod.rs index 35cc531e00..85b2b53c25 100644 --- a/editor/src/document/mod.rs +++ b/editor/src/document/mod.rs @@ -1,3 +1,4 @@ +mod artboard_message_handler; mod document_file; mod document_message_handler; pub mod layer_panel; @@ -17,5 +18,8 @@ pub use document_message_handler::{Clipboard, DocumentsMessage, DocumentsMessage pub use movement_handler::{MovementMessage, MovementMessageDiscriminant}; #[doc(inline)] pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant}; + +#[doc(inline)] +pub use artboard_message_handler::{ArtboardMessage, ArtboardMessageDiscriminant}; #[doc(inline)] pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant}; diff --git a/editor/src/document/movement_handler.rs b/editor/src/document/movement_handler.rs index e5d625bf82..0023f2b6c3 100644 --- a/editor/src/document/movement_handler.rs +++ b/editor/src/document/movement_handler.rs @@ -81,6 +81,7 @@ impl MovementMessageHandler { fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque) { let half_viewport = viewport_bounds.size() / 2.; let scaled_half_viewport = half_viewport / self.scale; + responses.push_back( DocumentOperation::SetLayerTransform { path: vec![], @@ -88,6 +89,17 @@ impl MovementMessageHandler { } .into(), ); + + responses.push_back( + ArtboardMessage::DispatchOperation( + DocumentOperation::SetLayerTransform { + path: vec![], + transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), + } + .into(), + ) + .into(), + ); } } diff --git a/editor/src/frontend/frontend_message_handler.rs b/editor/src/frontend/frontend_message_handler.rs index 65049c9fd6..5eb6b9ccf0 100644 --- a/editor/src/frontend/frontend_message_handler.rs +++ b/editor/src/frontend/frontend_message_handler.rs @@ -28,11 +28,12 @@ pub enum FrontendMessage { UpdateLayer { data: LayerPanelEntry }, UpdateArtwork { svg: String }, UpdateOverlays { svg: String }, + UpdateArtboards { svg: String }, UpdateScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) }, UpdateRulers { origin: (f64, f64), spacing: f64, interval: f64 }, ExportDocument { document: String, name: String }, SaveDocument { document: String, name: String }, - AutoSaveDocument { document: String, details: FrontendDocumentDetails }, + AutoSaveDocument { document: String, details: FrontendDocumentDetails, version: String }, RemoveAutoSaveDocument { document_id: u64 }, OpenDocumentBrowse, UpdateWorkingColors { primary: Color, secondary: Color }, diff --git a/editor/src/input/input_preprocessor.rs b/editor/src/input/input_preprocessor.rs index 678521be14..0fb4140f75 100644 --- a/editor/src/input/input_preprocessor.rs +++ b/editor/src/input/input_preprocessor.rs @@ -122,6 +122,16 @@ impl MessageHandler for InputPreprocessor { ) .into(), ); + responses.push_back( + DocumentMessage::Artboard( + graphene::Operation::TransformLayer { + path: vec![], + transform: glam::DAffine2::from_translation(translation).to_cols_array(), + } + .into(), + ) + .into(), + ); } } }; diff --git a/editor/src/lib.rs b/editor/src/lib.rs index b1ac444ddc..a976d37946 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -58,6 +58,7 @@ pub mod message_prelude { pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant}; pub use crate::communication::{ActionList, MessageHandler}; pub use crate::document::Clipboard; + pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant}; pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant}; pub use crate::document::{DocumentsMessage, DocumentsMessageDiscriminant}; pub use crate::document::{MovementMessage, MovementMessageDiscriminant}; diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index 7ec283b3b1..0e8c19a43b 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -124,6 +124,7 @@
+
@@ -233,13 +234,12 @@ // Fallback values if JS hasn't set these to integers yet width: 100%; height: 100%; + // Allows dev tools to select the artwork without being blocked by the SVG containers + pointer-events: none; - &.artwork { - background: #ffffff; - } - - &.overlays { - user-select: none; + // Prevent inheritance from reaching the child elements + > * { + pointer-events: auto; } } } @@ -251,7 +251,7 @@