Skip to content

Commit 54e9121

Browse files
mfish33otdaviesKeavon
authored
Implement artboards and document version enforcement (#466)
* - graphite document artboard implementation - autosave document load hitch fix - Autosave will delete saved files when graphite document version changes * formating * - top left 0,0 - fixed hitch on first document - vue calls first render * Revert * Merge branch 'master' into artboards * Small bug fixes and code review tweaks Co-authored-by: Oliver Davies <[email protected]> Co-authored-by: Keavon Chambers <[email protected]>
1 parent 11f15bd commit 54e9121

File tree

17 files changed

+218
-33
lines changed

17 files changed

+218
-33
lines changed

editor/src/consts.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg";
4343

4444
// COLORS
4545
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
46+
47+
// Document
48+
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.1";
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
pub use crate::document::layer_panel::*;
2+
use crate::document::{DocumentMessage, LayerMetadata};
3+
use crate::input::InputPreprocessor;
4+
use crate::message_prelude::*;
5+
use glam::{DAffine2, DVec2};
6+
use graphene::color::Color;
7+
use graphene::document::Document as GrapheneDocument;
8+
use graphene::layers::style::{self, Fill, ViewMode};
9+
use graphene::Operation as DocumentOperation;
10+
use serde::{Deserialize, Serialize};
11+
use std::collections::VecDeque;
12+
13+
#[impl_message(Message, DocumentMessage, Artboard)]
14+
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
15+
pub enum ArtboardMessage {
16+
DispatchOperation(Box<DocumentOperation>),
17+
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
18+
RenderArtboards,
19+
}
20+
21+
impl From<DocumentOperation> for ArtboardMessage {
22+
fn from(operation: DocumentOperation) -> Self {
23+
Self::DispatchOperation(Box::new(operation))
24+
}
25+
}
26+
27+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28+
pub struct ArtboardMessageHandler {
29+
pub artboards_graphene_document: GrapheneDocument,
30+
pub artboard_ids: Vec<LayerId>,
31+
}
32+
33+
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
34+
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
35+
// let (layer_metadata, document, ipp) = data;
36+
use ArtboardMessage::*;
37+
match message {
38+
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(&operation) {
39+
Ok(_) => (),
40+
Err(e) => log::error!("Artboard Error: {:?}", e),
41+
},
42+
AddArtboard { top, left, height, width } => {
43+
let artboard_id = generate_uuid();
44+
self.artboard_ids.push(artboard_id);
45+
46+
responses.push_back(
47+
ArtboardMessage::DispatchOperation(
48+
DocumentOperation::AddRect {
49+
path: vec![artboard_id],
50+
insert_index: -1,
51+
transform: DAffine2::from_scale_angle_translation(DVec2::new(height, width), 0., DVec2::new(top, left)).to_cols_array(),
52+
style: style::PathStyle::new(None, Some(Fill::new(Color::WHITE))),
53+
}
54+
.into(),
55+
)
56+
.into(),
57+
);
58+
}
59+
RenderArtboards => {}
60+
}
61+
62+
// Render an infinite canvas if there are no artboards
63+
if self.artboard_ids.is_empty() {
64+
responses.push_back(
65+
FrontendMessage::UpdateArtboards {
66+
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
67+
}
68+
.into(),
69+
)
70+
} else {
71+
responses.push_back(
72+
FrontendMessage::UpdateArtboards {
73+
svg: self.artboards_graphene_document.render_root(ViewMode::Normal),
74+
}
75+
.into(),
76+
);
77+
}
78+
}
79+
80+
fn actions(&self) -> ActionList {
81+
actions!(ArtBoardMessageDiscriminant;)
82+
}
83+
}

editor/src/document/document_file.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use std::collections::HashMap;
22
use std::collections::VecDeque;
33

4+
use super::artboard_message_handler::ArtboardMessage;
5+
use super::artboard_message_handler::ArtboardMessageHandler;
46
pub use super::layer_panel::*;
57
use super::movement_handler::{MovementMessage, MovementMessageHandler};
68
use super::overlay_message_handler::OverlayMessageHandler;
79
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
810
use super::vectorize_layer_metadata;
911

1012
use crate::consts::DEFAULT_DOCUMENT_NAME;
13+
use crate::consts::GRAPHITE_DOCUMENT_VERSION;
1114
use crate::consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING};
1215
use crate::document::Clipboard;
1316
use crate::input::InputPreprocessor;
@@ -83,10 +86,12 @@ pub struct DocumentMessageHandler {
8386
movement_handler: MovementMessageHandler,
8487
#[serde(skip)]
8588
overlay_message_handler: OverlayMessageHandler,
89+
artboard_message_handler: ArtboardMessageHandler,
8690
#[serde(skip)]
8791
transform_layer_handler: TransformLayerMessageHandler,
8892
pub snapping_enabled: bool,
8993
pub view_mode: ViewMode,
94+
pub version: String,
9095
}
9196

9297
impl Default for DocumentMessageHandler {
@@ -101,9 +106,11 @@ impl Default for DocumentMessageHandler {
101106
layer_range_selection_reference: Vec::new(),
102107
movement_handler: MovementMessageHandler::default(),
103108
overlay_message_handler: OverlayMessageHandler::default(),
109+
artboard_message_handler: ArtboardMessageHandler::default(),
104110
transform_layer_handler: TransformLayerMessageHandler::default(),
105111
snapping_enabled: true,
106112
view_mode: ViewMode::default(),
113+
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
107114
}
108115
}
109116
}
@@ -118,6 +125,8 @@ pub enum DocumentMessage {
118125
DispatchOperation(Box<DocumentOperation>),
119126
#[child]
120127
Overlay(OverlayMessage),
128+
#[child]
129+
Artboard(ArtboardMessage),
121130
UpdateLayerMetadata {
122131
layer_path: Vec<LayerId>,
123132
layer_metadata: LayerMetadata,
@@ -195,20 +204,34 @@ impl DocumentMessageHandler {
195204
}
196205

197206
pub fn deserialize_document(serialized_content: &str) -> Result<Self, DocumentError> {
198-
log::info!("Deserializing: {:?}", serialized_content);
199-
serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string()))
207+
let deserialized_result: Result<Self, DocumentError> = serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string()));
208+
match deserialized_result {
209+
Ok(document) => {
210+
if document.version != GRAPHITE_DOCUMENT_VERSION {
211+
Err(DocumentError::InvalidFile("Graphite document version mismatch".to_string()))
212+
} else {
213+
Ok(document)
214+
}
215+
}
216+
Err(e) => Err(e),
217+
}
200218
}
201219

202220
pub fn with_name(name: String, ipp: &InputPreprocessor) -> Self {
203221
let mut document = Self { name, ..Self::default() };
204-
document.graphene_document.root.transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
222+
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
223+
document.graphene_document.root.transform = starting_root_transform;
224+
document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform;
205225
document
206226
}
207227

208-
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
228+
pub fn with_name_and_content(name: String, serialized_content: String, ipp: &InputPreprocessor) -> Result<Self, EditorError> {
209229
match Self::deserialize_document(&serialized_content) {
210230
Ok(mut document) => {
211231
document.name = name;
232+
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
233+
document.graphene_document.root.transform = starting_root_transform;
234+
document.artboard_message_handler.artboards_graphene_document.root.transform = starting_root_transform;
212235
Ok(document)
213236
}
214237
Err(DocumentError::InvalidFile(msg)) => Err(EditorError::Document(msg)),
@@ -546,6 +569,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
546569
);
547570
// responses.push_back(OverlayMessage::RenderOverlays.into());
548571
}
572+
Artboard(message) => {
573+
self.artboard_message_handler.process_action(
574+
message,
575+
(Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp),
576+
responses,
577+
);
578+
}
549579
ExportDocument => {
550580
let bbox = self.graphene_document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
551581
let size = bbox[1] - bbox[0];
@@ -827,6 +857,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
827857
}
828858
.into(),
829859
);
860+
responses.push_back(ArtboardMessage::RenderArtboards.into());
861+
830862
let document_transform = &self.movement_handler;
831863

832864
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform.scale * SCALE_EFFECT;

editor/src/document/document_message_handler.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{DocumentMessageHandler, LayerMetadata};
2-
use crate::consts::DEFAULT_DOCUMENT_NAME;
2+
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
33
use crate::frontend::frontend_message_handler::FrontendDocumentDetails;
44
use crate::input::InputPreprocessor;
55
use crate::message_prelude::*;
@@ -196,6 +196,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
196196
for layer in self.active_document().layer_metadata.keys() {
197197
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
198198
}
199+
responses.push_back(ToolMessage::DocumentIsDirty.into());
199200
}
200201
CloseActiveDocumentWithConfirmation => {
201202
responses.push_back(DocumentsMessage::CloseDocumentWithConfirmation(self.active_document_id).into());
@@ -293,7 +294,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
293294
document,
294295
document_is_saved,
295296
} => {
296-
let document = DocumentMessageHandler::with_name_and_content(document_name, document);
297+
let document = DocumentMessageHandler::with_name_and_content(document_name, document, ipp);
297298
match document {
298299
Ok(mut document) => {
299300
document.set_save_state(document_is_saved);
@@ -333,6 +334,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
333334
id,
334335
name: document.name.clone(),
335336
},
337+
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
336338
}
337339
.into(),
338340
)

editor/src/document/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod artboard_message_handler;
12
mod document_file;
23
mod document_message_handler;
34
pub mod layer_panel;
@@ -17,5 +18,8 @@ pub use document_message_handler::{Clipboard, DocumentsMessage, DocumentsMessage
1718
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
1819
#[doc(inline)]
1920
pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant};
21+
22+
#[doc(inline)]
23+
pub use artboard_message_handler::{ArtboardMessage, ArtboardMessageDiscriminant};
2024
#[doc(inline)]
2125
pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant};

editor/src/document/movement_handler.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,25 @@ impl MovementMessageHandler {
8181
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
8282
let half_viewport = viewport_bounds.size() / 2.;
8383
let scaled_half_viewport = half_viewport / self.scale;
84+
8485
responses.push_back(
8586
DocumentOperation::SetLayerTransform {
8687
path: vec![],
8788
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
8889
}
8990
.into(),
9091
);
92+
93+
responses.push_back(
94+
ArtboardMessage::DispatchOperation(
95+
DocumentOperation::SetLayerTransform {
96+
path: vec![],
97+
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
98+
}
99+
.into(),
100+
)
101+
.into(),
102+
);
91103
}
92104
}
93105

editor/src/frontend/frontend_message_handler.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ pub enum FrontendMessage {
2828
UpdateLayer { data: LayerPanelEntry },
2929
UpdateArtwork { svg: String },
3030
UpdateOverlays { svg: String },
31+
UpdateArtboards { svg: String },
3132
UpdateScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
3233
UpdateRulers { origin: (f64, f64), spacing: f64, interval: f64 },
3334
ExportDocument { document: String, name: String },
3435
SaveDocument { document: String, name: String },
35-
AutoSaveDocument { document: String, details: FrontendDocumentDetails },
36+
AutoSaveDocument { document: String, details: FrontendDocumentDetails, version: String },
3637
RemoveAutoSaveDocument { document_id: u64 },
3738
OpenDocumentBrowse,
3839
UpdateWorkingColors { primary: Color, secondary: Color },

editor/src/input/input_preprocessor.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
122122
)
123123
.into(),
124124
);
125+
responses.push_back(
126+
DocumentMessage::Artboard(
127+
graphene::Operation::TransformLayer {
128+
path: vec![],
129+
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
130+
}
131+
.into(),
132+
)
133+
.into(),
134+
);
125135
}
126136
}
127137
};

editor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub mod message_prelude {
5858
pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant};
5959
pub use crate::communication::{ActionList, MessageHandler};
6060
pub use crate::document::Clipboard;
61+
pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant};
6162
pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant};
6263
pub use crate::document::{DocumentsMessage, DocumentsMessageDiscriminant};
6364
pub use crate::document::{MovementMessage, MovementMessageDiscriminant};

frontend/src/components/panels/Document.vue

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
</LayoutCol>
125125
<LayoutCol :class="'canvas-area'">
126126
<div class="canvas" ref="canvas">
127+
<svg class="artboards" v-html="artboardSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
127128
<svg class="artwork" v-html="artworkSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
128129
<svg class="overlays" v-html="overlaysSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
129130
</div>
@@ -233,13 +234,12 @@
233234
// Fallback values if JS hasn't set these to integers yet
234235
width: 100%;
235236
height: 100%;
237+
// Allows dev tools to select the artwork without being blocked by the SVG containers
238+
pointer-events: none;
236239
237-
&.artwork {
238-
background: #ffffff;
239-
}
240-
241-
&.overlays {
242-
user-select: none;
240+
// Prevent inheritance from reaching the child elements
241+
> * {
242+
pointer-events: auto;
243243
}
244244
}
245245
}
@@ -251,7 +251,7 @@
251251
<script lang="ts">
252252
import { defineComponent } from "vue";
253253
254-
import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation, ToolName } from "@/dispatcher/js-messages";
254+
import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation, ToolName, UpdateArtboards } from "@/dispatcher/js-messages";
255255
256256
import LayoutCol from "@/components/layout/LayoutCol.vue";
257257
import LayoutRow from "@/components/layout/LayoutRow.vue";
@@ -338,6 +338,10 @@ export default defineComponent({
338338
this.overlaysSvg = updateOverlays.svg;
339339
});
340340
341+
this.editor.dispatcher.subscribeJsMessage(UpdateArtboards, (updateArtboards) => {
342+
this.artboardSvg = updateArtboards.svg;
343+
});
344+
341345
this.editor.dispatcher.subscribeJsMessage(UpdateScrollbars, (updateScrollbars) => {
342346
this.scrollbarPos = updateScrollbars.position;
343347
this.scrollbarSize = updateScrollbars.size;
@@ -383,6 +387,7 @@ export default defineComponent({
383387
384388
return {
385389
artworkSvg: "",
390+
artboardSvg: "",
386391
overlaysSvg: "",
387392
canvasSvgWidth: "100%",
388393
canvasSvgHeight: "100%",

0 commit comments

Comments
 (0)