Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
83 changes: 83 additions & 0 deletions editor/src/document/artboard_message_handler.rs
Original file line number Diff line number Diff line change
@@ -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<DocumentOperation>),
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
RenderArtboards,
}

impl From<DocumentOperation> 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<LayerId>,
}

impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
// 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##"<rect width="100%" height="100%" fill="#ffffff" />"##.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;)
}
}
40 changes: 36 additions & 4 deletions editor/src/document/document_file.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
}
}
}
Expand All @@ -118,6 +125,8 @@ pub enum DocumentMessage {
DispatchOperation(Box<DocumentOperation>),
#[child]
Overlay(OverlayMessage),
#[child]
Artboard(ArtboardMessage),
UpdateLayerMetadata {
layer_path: Vec<LayerId>,
layer_metadata: LayerMetadata,
Expand Down Expand Up @@ -195,20 +204,34 @@ impl DocumentMessageHandler {
}

pub fn deserialize_document(serialized_content: &str) -> Result<Self, DocumentError> {
log::info!("Deserializing: {:?}", serialized_content);
serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string()))
let deserialized_result: Result<Self, DocumentError> = 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<Self, EditorError> {
pub fn with_name_and_content(name: String, serialized_content: String, ipp: &InputPreprocessor) -> Result<Self, EditorError> {
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)),
Expand Down Expand Up @@ -546,6 +569,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> 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];
Expand Down Expand Up @@ -827,6 +857,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> 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;
Expand Down
6 changes: 4 additions & 2 deletions editor/src/document/document_message_handler.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -196,6 +196,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> 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());
Expand Down Expand Up @@ -293,7 +294,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> 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);
Expand Down Expand Up @@ -333,6 +334,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
id,
name: document.name.clone(),
},
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
}
.into(),
)
Expand Down
4 changes: 4 additions & 0 deletions editor/src/document/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod artboard_message_handler;
mod document_file;
mod document_message_handler;
pub mod layer_panel;
Expand All @@ -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};
12 changes: 12 additions & 0 deletions editor/src/document/movement_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,25 @@ impl MovementMessageHandler {
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
let half_viewport = viewport_bounds.size() / 2.;
let scaled_half_viewport = half_viewport / self.scale;

responses.push_back(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
);

responses.push_back(
ArtboardMessage::DispatchOperation(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
)
.into(),
);
}
}

Expand Down
3 changes: 2 additions & 1 deletion editor/src/frontend/frontend_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
10 changes: 10 additions & 0 deletions editor/src/input/input_preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
)
.into(),
);
responses.push_back(
DocumentMessage::Artboard(
graphene::Operation::TransformLayer {
path: vec![],
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
}
.into(),
)
.into(),
);
}
}
};
Expand Down
1 change: 1 addition & 0 deletions editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/components/panels/Document.vue
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
</LayoutCol>
<LayoutCol :class="'canvas-area'">
<div class="canvas" ref="canvas">
<svg class="artboards" v-html="artboardSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
<svg class="artwork" v-html="artworkSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
<svg class="overlays" v-html="overlaysSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
</div>
Expand Down Expand Up @@ -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;
}
}
}
Expand All @@ -251,7 +251,7 @@
<script lang="ts">
import { defineComponent } from "vue";

import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation, ToolName } from "@/dispatcher/js-messages";
import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation, ToolName, UpdateArtboards } from "@/dispatcher/js-messages";

import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
Expand Down Expand Up @@ -338,6 +338,10 @@ export default defineComponent({
this.overlaysSvg = updateOverlays.svg;
});

this.editor.dispatcher.subscribeJsMessage(UpdateArtboards, (updateArtboards) => {
this.artboardSvg = updateArtboards.svg;
});

this.editor.dispatcher.subscribeJsMessage(UpdateScrollbars, (updateScrollbars) => {
this.scrollbarPos = updateScrollbars.position;
this.scrollbarSize = updateScrollbars.size;
Expand Down Expand Up @@ -383,6 +387,7 @@ export default defineComponent({

return {
artworkSvg: "",
artboardSvg: "",
overlaysSvg: "",
canvasSvgWidth: "100%",
canvasSvgHeight: "100%",
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/widgets/inputs/MenuBarInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
ref: undefined,
children: [
[
{ label: "New", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: async (): Promise<void> => editor.instance.new_document() },
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: async (): Promise<void> => editor.instance.open_document() },
{ label: "New", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: (): void => editor.instance.new_document() },
{
label: "New 1920x1080",
icon: "File",
action: (): void => {
editor.instance.new_document();
editor.instance.create_artboard(0, 0, 1920, 1080);
},
},
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: (): void => editor.instance.open_document() },
{
label: "Open Recent",
shortcut: ["KeyControl", "KeyShift", "KeyO"],
Expand Down
Loading