diff --git a/editor/src/communication/dispatcher.rs b/editor/src/communication/dispatcher.rs index 2d776124..58e0f7f9 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/communication/dispatcher.rs @@ -25,9 +25,9 @@ struct DispatcherMessageHandlers { tool_message_handler: ToolMessageHandler, } -// For optimization, these are messages guaranteed to be redundant when repeated. -// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. -// In addition, these messages do not change any state in the backend (aside from caches). +/// For optimization, these are messages guaranteed to be redundant when repeated. +/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. +/// In addition, these messages do not change any state in the backend (aside from caches). const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Rerender))), diff --git a/editor/src/frontend/frontend_message.rs b/editor/src/frontend/frontend_message.rs index 7b986d5a..023de496 100644 --- a/editor/src/frontend/frontend_message.rs +++ b/editor/src/frontend/frontend_message.rs @@ -29,6 +29,7 @@ pub enum FrontendMessage { TriggerIndexedDbRemoveDocument { document_id: u64 }, TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String }, TriggerTextCommit, + TriggerViewportResize, // Update prefix: give the frontend a new value or state for it to use UpdateActiveDocument { document_id: u64 }, diff --git a/editor/src/input/input_mapper.rs b/editor/src/input/input_mapper.rs index c4819b8e..346f24e3 100644 --- a/editor/src/input/input_mapper.rs +++ b/editor/src/input/input_mapper.rs @@ -23,7 +23,7 @@ impl Default for Mapping { use Key::*; // WARNING! - // If a new mapping isn't being handled (and perhaps another lower-precedence one is instead), make sure to advertise + // If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise // it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`). let mappings = mapping![ @@ -145,7 +145,6 @@ impl Default for Mapping { entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]}, entry! {action=DocumentMessage::CreateEmptyFolder { container_path: vec![] }, key_down=KeyN, modifiers=[KeyControl, KeyShift]}, entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete}, - entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX}, entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]}, entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]}, diff --git a/editor/src/input/input_preprocessor_message_handler.rs b/editor/src/input/input_preprocessor_message_handler.rs index 24dde1c7..61811a15 100644 --- a/editor/src/input/input_preprocessor_message_handler.rs +++ b/editor/src/input/input_preprocessor_message_handler.rs @@ -47,6 +47,7 @@ impl MessageHandler for InputPreprocessorMessageHa ) .into(), ); + responses.push_back(FrontendMessage::TriggerViewportResize.into()); } } InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { diff --git a/editor/src/viewport_tools/tools/path.rs b/editor/src/viewport_tools/tools/path.rs index d40709d0..e4b0dd16 100644 --- a/editor/src/viewport_tools/tools/path.rs +++ b/editor/src/viewport_tools/tools/path.rs @@ -10,9 +10,9 @@ use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; -use glam::DVec2; use graphene::intersection::Quad; +use glam::DVec2; use serde::{Deserialize, Serialize}; #[derive(Default)] diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 83f0d3e4..9041ee59 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -270,7 +270,10 @@ export default defineComponent({ }; }, data() { + // Initialize the Graphite WASM editor instance const editor = createEditorState(); + + // Initialize other stateful Vue systems const dialog = createDialogState(editor); const documents = createDocumentsState(editor, dialog); const fullscreen = createFullscreenState(); diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index c4ef737b..40045e8f 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -65,11 +65,11 @@ - + - +
@@ -240,6 +240,7 @@ import { defaultWidgetLayout, UpdateDocumentBarLayout, TriggerTextCommit, + TriggerViewportResize, DisplayRemoveEditableTextbox, DisplayEditableTextbox, } from "@/dispatcher/js-messages"; @@ -263,7 +264,10 @@ export default defineComponent({ inject: ["editor", "dialog"], methods: { viewportResize() { + // Resize the canvas + const canvas = this.$refs.canvas as HTMLElement; + // Get the width and height rounded up to the nearest even number because resizing is centered and dividing an odd number by 2 for centering causes antialiasing let width = Math.ceil(parseFloat(getComputedStyle(canvas).width)); if (width % 2 === 1) width += 1; @@ -272,6 +276,13 @@ export default defineComponent({ this.canvasSvgWidth = `${width}px`; this.canvasSvgHeight = `${height}px`; + + // Resize the rulers + + const rulerHorizontal = this.$refs.rulerHorizontal as typeof CanvasRuler; + const rulerVertical = this.$refs.rulerVertical as typeof CanvasRuler; + if (rulerHorizontal) rulerHorizontal.handleResize(); + if (rulerVertical) rulerVertical.handleResize(); }, translateCanvasX(newValue: number) { const delta = newValue - this.scrollbarPos.x; @@ -415,9 +426,7 @@ export default defineComponent({ this.editor.dispatcher.subscribeJsMessage(UpdateDocumentBarLayout, (updateDocumentBarLayout) => { this.documentBarLayout = updateDocumentBarLayout; }); - - window.addEventListener("resize", this.viewportResize); - window.addEventListener("DOMContentLoaded", this.viewportResize); + this.editor.dispatcher.subscribeJsMessage(TriggerViewportResize, this.viewportResize); }, data() { const documentModeEntries: SectionsOfMenuListEntries = [ diff --git a/frontend/src/components/widgets/rulers/CanvasRuler.vue b/frontend/src/components/widgets/rulers/CanvasRuler.vue index d22ddc95..79f5abd1 100644 --- a/frontend/src/components/widgets/rulers/CanvasRuler.vue +++ b/frontend/src/components/widgets/rulers/CanvasRuler.vue @@ -138,13 +138,6 @@ export default defineComponent({ } }, }, - mounted() { - window.addEventListener("resize", this.handleResize); - this.handleResize(); - }, - beforeUnmount() { - window.removeEventListener("resize", this.handleResize); - }, data() { return { rulerLength: 0, diff --git a/frontend/src/dispatcher/js-messages.ts b/frontend/src/dispatcher/js-messages.ts index 6460c785..db0fa3f4 100644 --- a/frontend/src/dispatcher/js-messages.ts +++ b/frontend/src/dispatcher/js-messages.ts @@ -471,41 +471,44 @@ export class DisplayDialogComingSoon extends JsMessage { export class TriggerTextCommit extends JsMessage {} +export class TriggerViewportResize extends JsMessage {} + // Any is used since the type of the object should be known from the rust side // eslint-disable-next-line @typescript-eslint/no-explicit-any type JSMessageFactory = (data: any, wasm: WasmInstance, instance: RustEditorInstance) => JsMessage; type MessageMaker = typeof JsMessage | JSMessageFactory; export const messageConstructors: Record = { - UpdateDocumentArtwork, - UpdateDocumentOverlays, - UpdateDocumentScrollbars, - UpdateDocumentRulers, - TriggerFileDownload, - TriggerFileUpload, + DisplayConfirmationToCloseAllDocuments, + DisplayConfirmationToCloseDocument, + DisplayDialogAboutGraphite, + DisplayDialogComingSoon, + DisplayDialogError, + DisplayDialogPanic, DisplayDocumentLayerTreeStructure: newDisplayDocumentLayerTreeStructure, DisplayEditableTextbox, DisplayRemoveEditableTextbox, - UpdateDocumentLayer, - UpdateActiveTool, - UpdateActiveDocument, - UpdateOpenDocumentsList, - UpdateInputHints, - UpdateWorkingColors, - UpdateCanvasZoom, - UpdateCanvasRotation, - UpdateMouseCursor, - DisplayDialogError, - DisplayDialogPanic, - DisplayConfirmationToCloseDocument, - DisplayConfirmationToCloseAllDocuments, - DisplayDialogAboutGraphite, - TriggerIndexedDbWriteDocument, + TriggerFileDownload, + TriggerFileUpload, TriggerIndexedDbRemoveDocument, + TriggerIndexedDbWriteDocument, TriggerTextCommit, + TriggerViewportResize, + UpdateActiveDocument, + UpdateActiveTool, + UpdateCanvasRotation, + UpdateCanvasZoom, UpdateDocumentArtboards, - UpdateToolOptionsLayout, - DisplayDialogComingSoon, + UpdateDocumentArtwork, UpdateDocumentBarLayout, + UpdateDocumentLayer, + UpdateDocumentOverlays, + UpdateDocumentRulers, + UpdateDocumentScrollbars, + UpdateInputHints, + UpdateMouseCursor, + UpdateOpenDocumentsList, + UpdateToolOptionsLayout, + UpdateWorkingColors, } as const; export type JsMessageType = keyof typeof messageConstructors; diff --git a/frontend/src/state/wasm-loader.ts b/frontend/src/state/wasm-loader.ts index 3d6ad20a..538de8d9 100644 --- a/frontend/src/state/wasm-loader.ts +++ b/frontend/src/state/wasm-loader.ts @@ -6,6 +6,7 @@ import { JsMessageType } from "@/dispatcher/js-messages"; export type WasmInstance = typeof import("@/../wasm/pkg"); export type RustEditorInstance = InstanceType; +// `wasmImport` starts uninitialized until `initWasm()` is called in `main.ts` before the Vue app is created let wasmImport: WasmInstance | null = null; export async function initWasm(): Promise { if (wasmImport !== null) return; @@ -62,20 +63,24 @@ export function getWasmInstance(): WasmInstance { throw new Error("Editor WASM backend was not initialized at application startup"); } -type CreateEditorStateType = { dispatcher: ReturnType; rawWasm: WasmInstance; instance: RustEditorInstance }; +type CreateEditorStateType = { + /// Allows subscribing to messages from the WASM backend + rawWasm: WasmInstance; + /// Bindings to WASM wrapper declarations (generated by wasm-bindgen) + dispatcher: ReturnType; + /// WASM wrapper's exported functions (generated by wasm-bindgen) + instance: RustEditorInstance; +}; export function createEditorState(): CreateEditorStateType { - const dispatcher = createJsDispatcher(); const rawWasm = getWasmInstance(); - - const rustCallback = (messageType: JsMessageType, data: Record): void => { + const dispatcher = createJsDispatcher(); + const instance = new rawWasm.JsEditorHandle((messageType: JsMessageType, data: Record): void => { dispatcher.handleJsMessage(messageType, data, rawWasm, instance); - }; - - const instance = new rawWasm.JsEditorHandle(rustCallback); + }); return { - dispatcher, rawWasm, + dispatcher, instance, }; }