diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index b97d9511..068b776b 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -119,7 +119,7 @@ impl Dispatcher { } Message::Frontend(message) => { // Handle these messages immediately by returning early - if let FrontendMessage::TriggerFontLoad { .. } | FrontendMessage::TriggerRefreshBoundsOfViewports = message { + if let FrontendMessage::TriggerFontLoad { .. } = message { self.responses.push(message); self.cleanup_queues(false); diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 058c0e3e..cacb4295 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -4,7 +4,7 @@ use crate::messages::prelude::*; use graph_craft::document::NodeId; use graphene_core::uuid::generate_uuid; -use glam::{DVec2, IVec2, UVec2}; +use glam::{IVec2, UVec2}; /// A dialog to allow users to set some initial options about a new document. #[derive(Debug, Clone, Default)] @@ -26,16 +26,13 @@ impl MessageHandler for NewDocumentDialogMessageHa let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0; if create_artboard { - let id = NodeId(generate_uuid()); responses.add(GraphOperationMessage::NewArtboard { - id, + id: NodeId(generate_uuid()), artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), }); - responses.add(NavigationMessage::FitViewportToBounds { - bounds: [DVec2::ZERO, self.dimensions.as_dvec2()], - prevent_zoom_past_100: true, - }); + responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); } + responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::UpdateNewNodeGraph); } diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index c191f967..59e5c9b8 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -46,6 +46,7 @@ pub enum FrontendMessage { #[serde(rename = "blobUrl")] blob_url: String, }, + TriggerDelayedZoomCanvasToFitAll, TriggerDownloadBlobUrl { #[serde(rename = "layerName")] layer_name: String, @@ -87,7 +88,6 @@ pub enum FrontendMessage { TriggerLoadPreferences, TriggerOpenDocument, TriggerPaste, - TriggerRefreshBoundsOfViewports, TriggerRevokeBlobUrl { url: String, }, @@ -112,7 +112,6 @@ pub enum FrontendMessage { #[serde(rename = "documentSerializedContent")] document_serialized_content: String, }, - TriggerViewportResize, TriggerVisitLink { url: String, }, diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 2da166bf..4939c221 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -31,7 +31,6 @@ impl MessageHandler for self.viewport_bounds = bounds; responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); - responses.add(FrontendMessage::TriggerViewportResize); } } InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 16903249..d38b5ebe 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -429,7 +429,6 @@ impl MessageHandler> for DocumentMessag } responses.add(FrontendMessage::TriggerGraphViewOverlay { open }); - responses.add(FrontendMessage::TriggerRefreshBoundsOfViewports); // Update the tilt menu bar buttons to be disabled when the graph is open responses.add(MenuBarMessage::SendLayout); if open { @@ -780,7 +779,7 @@ impl MessageHandler> for DocumentMessag responses.add(NodeGraphMessage::UpdateNewNodeGraph); } DocumentMessage::RenderRulers => { - let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom); + let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()); let ruler_origin = if !self.graph_view_overlay_open { self.metadata().document_to_viewport.transform_point2(DVec2::ZERO) @@ -802,7 +801,7 @@ impl MessageHandler> for DocumentMessag }); } DocumentMessage::RenderScrollbars => { - let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom); + let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()); let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; @@ -1871,7 +1870,7 @@ impl DocumentMessageHandler { .tooltip("Reset Tilt and Zoom to 100%") .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) - .disabled(self.document_ptz.tilt.abs() < 1e-4 && (self.document_ptz.zoom - 1.).abs() < 1e-4) + .disabled(self.document_ptz.tilt.abs() < 1e-4 && (self.document_ptz.zoom() - 1.).abs() < 1e-4) .widget_holder(), PopoverButton::new() .popover_layout(vec![ @@ -1902,7 +1901,7 @@ impl DocumentMessageHandler { ]) .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.document_ptz.zoom) * 100.)) + NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.document_ptz.zoom()) * 100.)) .unit("%") .min(0.000001) .max(1000000.) diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index a1db6a2c..d78be31e 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -50,7 +50,7 @@ impl MessageHandler> for Navigation } else { node_graph_ptz.entry(node_graph_handler.network.clone()).or_insert(PTZ::default()) }; - let old_zoom = ptz.zoom; + let old_zoom = ptz.zoom(); match message { NavigationMessage::BeginCanvasPan => { @@ -110,8 +110,8 @@ impl MessageHandler> for Navigation }); self.navigation_operation = NavigationOperation::Zoom { - zoom_raw_not_snapped: ptz.zoom, - zoom_original_for_abort: ptz.zoom, + zoom_raw_not_snapped: ptz.zoom(), + zoom_original_for_abort: ptz.zoom(), snap: false, }; self.mouse_position = ipp.mouse.position; @@ -145,7 +145,7 @@ impl MessageHandler> for Navigation } NavigationMessage::CanvasTiltResetAndZoomTo100Percent => { ptz.tilt = 0.; - ptz.zoom = 1.; + ptz.set_zoom(1.); responses.add(PortfolioMessage::UpdateDocumentWidgets); self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); } @@ -154,16 +154,16 @@ impl MessageHandler> for Navigation self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); } NavigationMessage::CanvasZoomDecrease { center_on_mouse } => { - let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom).unwrap_or(&ptz.zoom); + let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom()).unwrap_or(&ptz.zoom()); if center_on_mouse { - responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position)); + responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position)); } responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); } NavigationMessage::CanvasZoomIncrease { center_on_mouse } => { - let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom).unwrap_or(&ptz.zoom); + let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom()).unwrap_or(&ptz.zoom()); if center_on_mouse { - responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position)); + responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position)); } responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); } @@ -179,10 +179,12 @@ impl MessageHandler> for Navigation } else { node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport) }; - zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp); + zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, ipp); responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position)); - responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom * zoom_factor }); + responses.add(NavigationMessage::CanvasZoomSet { + zoom_factor: ptz.zoom() * zoom_factor, + }); } NavigationMessage::CanvasZoomSet { zoom_factor } => { let document_bounds = if !graph_view_overlay_open { @@ -191,8 +193,9 @@ impl MessageHandler> for Navigation } else { node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport) }; - ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); - ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp); + let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); + let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp); + ptz.set_zoom(zoom); responses.add(PortfolioMessage::UpdateDocumentWidgets); self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); } @@ -208,7 +211,7 @@ impl MessageHandler> for Navigation ptz.pan = pan_original_for_abort; } NavigationOperation::Zoom { zoom_original_for_abort, .. } => { - ptz.zoom = zoom_original_for_abort; + ptz.set_zoom(zoom_original_for_abort); } } @@ -217,7 +220,7 @@ impl MessageHandler> for Navigation // Final chance to apply snapping if the key was pressed during this final frame ptz.tilt = self.snapped_tilt(ptz.tilt); - ptz.zoom = self.snapped_zoom(ptz.zoom); + ptz.set_zoom(self.snapped_zoom(ptz.zoom())); // Reset the navigation operation now that it's done self.navigation_operation = NavigationOperation::None; @@ -238,19 +241,18 @@ impl MessageHandler> for Navigation bounds: [pos1, pos2], prevent_zoom_past_100, } => { - let v1 = if !graph_view_overlay_open { - metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO) - } else { - node_graph_to_viewport.inverse().transform_point2(DVec2::ZERO) - }; - let v2 = if !graph_view_overlay_open { - metadata.document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size()) - } else { - node_graph_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size()) - }; + let (pos1, pos2) = (pos1.min(pos2), pos1.max(pos2)); + let diagonal = pos2 - pos1; - let center = ((v1 + v2) - (pos1 + pos2)) / 2.; - let size = 1. / ((pos2 - pos1) / (v2 - v1)); + if diagonal.length() < f64::EPSILON * 1000. || ipp.viewport_bounds.size() == DVec2::ZERO { + return; + } + + let transform = (if graph_view_overlay_open { *node_graph_to_viewport } else { metadata.document_to_viewport }).inverse(); + let (v1, v2) = (transform.transform_point2(DVec2::ZERO), transform.transform_point2(ipp.viewport_bounds.size())); + + let center = ((v2 + v1) - (pos2 + pos1)) / 2.; + let size = (v2 - v1) / diagonal; let new_scale = size.min_element(); let viewport_change = if !graph_view_overlay_open { @@ -264,12 +266,12 @@ impl MessageHandler> for Navigation ptz.pan += center; } - ptz.zoom *= new_scale * VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR; + ptz.set_zoom(ptz.zoom() * new_scale * VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR); // Keep the canvas filling less than the full available viewport bounds if requested. // And if the zoom is close to the full viewport bounds, we ignore the padding because 100% is preferrable if it still fits. - if prevent_zoom_past_100 && ptz.zoom > VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR { - ptz.zoom = 1.; + if prevent_zoom_past_100 && ptz.zoom() > VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR { + ptz.set_zoom(1.); } responses.add(PortfolioMessage::UpdateDocumentWidgets); @@ -339,7 +341,7 @@ impl MessageHandler> for Navigation updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp) }; - ptz.zoom = self.snapped_zoom(zoom_raw_not_snapped); + ptz.set_zoom(self.snapped_zoom(zoom_raw_not_snapped)); let snap = ipp.keyboard.get(snap as usize); @@ -349,7 +351,7 @@ impl MessageHandler> for Navigation snap, }; - responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom }); + responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() }); } } @@ -427,7 +429,7 @@ impl NavigationMessageHandler { } fn create_document_transform(&self, viewport_center: DVec2, ptz: &PTZ, responses: &mut VecDeque) { - let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom); + let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom()); responses.add(DocumentMessage::UpdateDocumentTransform { transform }); } diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 1fd0d426..2ea0467c 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -24,7 +24,13 @@ impl MessageHandler> for OverlaysMessag use super::utility_types::OverlayContext; use wasm_bindgen::JsCast; - let canvas = self.canvas.get_or_insert_with(|| overlay_canvas_element().expect("Failed to get canvas element")); + let canvas = match &self.canvas { + Some(canvas) => canvas, + None => { + let Some(new_canvas) = overlay_canvas_element() else { return }; + self.canvas.get_or_insert(new_canvas) + } + }; let context = self.context.get_or_insert_with(|| { let context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context"); diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index ddd744d3..4adb86b6 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -246,7 +246,7 @@ impl GridSnapping { pub fn compute_rectangle_spacing(mut size: DVec2, navigation: &PTZ) -> Option { let mut iterations = 0; size = size.abs(); - while (size * navigation.zoom).cmplt(DVec2::splat(10.)).any() { + while (size * navigation.zoom()).cmplt(DVec2::splat(10.)).any() { if iterations > 100 { return None; } @@ -261,7 +261,7 @@ impl GridSnapping { let length = length.abs(); let mut iterations = 0; let mut multiplier = 1.; - while (length / divisor.abs().max(1.)) * multiplier * navigation.zoom < 10. { + while (length / divisor.abs().max(1.)) * multiplier * navigation.zoom() < 10. { if iterations > 100 { return None; } @@ -409,8 +409,7 @@ impl fmt::Display for SnappingOptions { pub struct PTZ { pub pan: DVec2, pub tilt: f64, - // TODO: Make this private and add getter/setter methods which ensure zoom is always positive and greater than the smallest zoom level in `VIEWPORT_ZOOM_LEVELS`. - pub zoom: f64, + zoom: f64, } impl Default for PTZ { @@ -418,3 +417,13 @@ impl Default for PTZ { Self { pan: DVec2::ZERO, tilt: 0., zoom: 1. } } } + +impl PTZ { + pub fn zoom(&self) -> f64 { + self.zoom + } + + pub fn set_zoom(&mut self, zoom: f64) { + self.zoom = zoom.clamp(crate::consts::VIEWPORT_ZOOM_SCALE_MIN, crate::consts::VIEWPORT_ZOOM_SCALE_MAX) + } +} diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 5dfa3603..9d079386 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -618,7 +618,6 @@ impl MessageHandler> for PortfolioMes responses.add(DocumentMessage::RenderRulers); responses.add(MenuBarMessage::SendLayout); - responses.add(FrontendMessage::TriggerRefreshBoundsOfViewports); } } PortfolioMessage::UpdateDocumentWidgets => { diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 7410cda8..21e9e4df 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -64,7 +64,7 @@ impl SnapConstraint { } } pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 { - document.snapping_state.tolerance / document.document_ptz.zoom + document.snapping_state.tolerance / document.document_ptz.zoom() } fn compare_points(a: &&SnappedPoint, b: &&SnappedPoint) -> Ordering { diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index c225754e..c3ff0f1f 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -151,7 +151,6 @@ impl MessageHandler> for ToolMessageHandler { // Notify the frontend about the initial working colors document_data.update_working_colors(responses); - responses.add(FrontendMessage::TriggerRefreshBoundsOfViewports); let mut data = ToolActionHandlerData { document, diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index 2a478647..4cf07126 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -4,6 +4,7 @@ import type { DocumentState } from "@graphite/state-providers/document"; import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry"; import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization"; + import { updateBoundsOfViewports } from "@graphite/utility-functions/viewports"; import type { Editor } from "@graphite/wasm-communication/editor"; import { type MouseCursorIcon, @@ -12,7 +13,6 @@ DisplayEditableTextboxTransform, DisplayRemoveEditableTextbox, TriggerTextCommit, - TriggerViewportResize, UpdateDocumentArtwork, UpdateDocumentRulers, UpdateDocumentScrollbars, @@ -344,19 +344,6 @@ showTextInput = false; } - // Resize elements to render the new viewport size - export function viewportResize() { - if (!viewport) return; - - // Resize the canvas - canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(viewport).width)); - canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(viewport).height)); - - // Resize the rulers - rulerHorizontal?.resize(); - rulerVertical?.resize(); - } - onMount(() => { // Update rendered SVGs editor.subscriptions.subscribeJsMessage(UpdateDocumentArtwork, async (data) => { @@ -418,15 +405,24 @@ displayRemoveEditableTextbox(); }); - // Resize elements to render the new viewport size - editor.subscriptions.subscribeJsMessage(TriggerViewportResize, async () => { - await tick(); - - viewportResize(); - }); - // Once this component is mounted, we want to resend the document bounds to the backend via the resize event handler which does that window.dispatchEvent(new Event("resize")); + + const viewportResizeObserver = new ResizeObserver(() => { + if (!viewport) return; + + // Resize the canvas + canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(viewport).width)); + canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(viewport).height)); + + // Resize the rulers + rulerHorizontal?.resize(); + rulerVertical?.resize(); + + // Send the new bounds of the viewports to the backend + if (viewport.parentElement) updateBoundsOfViewports(editor, viewport.parentElement); + }); + if (viewport) viewportResizeObserver.observe(viewport); }); diff --git a/frontend/src/components/window/workspace/Workspace.svelte b/frontend/src/components/window/workspace/Workspace.svelte index d66fe735..584e4664 100644 --- a/frontend/src/components/window/workspace/Workspace.svelte +++ b/frontend/src/components/window/workspace/Workspace.svelte @@ -78,8 +78,6 @@ panelSizes[nextSiblingName] = ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100; panelSizes[prevSiblingName] = ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100; - - window.dispatchEvent(new CustomEvent("resize")); }; const cleanup = (e: PointerEvent) => { diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index 6fcbd260..d379cd48 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -8,6 +8,7 @@ import { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode } import { platformIsMac } from "@graphite/utility-functions/platform"; import { extractPixelData } from "@graphite/utility-functions/rasterization"; import { stripIndents } from "@graphite/utility-functions/strip-indents"; +import { updateBoundsOfViewports } from "@graphite/utility-functions/viewports"; import { type Editor } from "@graphite/wasm-communication/editor"; import { TriggerPaste } from "@graphite/wasm-communication/messages"; @@ -29,7 +30,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // eslint-disable-next-line @typescript-eslint/no-explicit-any const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: AddEventListenerOptions }[] = [ - { target: window, eventName: "resize", action: () => onWindowResize(window.document.body) }, + { target: window, eventName: "resize", action: () => updateBoundsOfViewports(editor, window.document.body) }, { target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) }, { target: window, eventName: "keyup", action: (e: KeyboardEvent) => onKeyUp(e) }, { target: window, eventName: "keydown", action: (e: KeyboardEvent) => onKeyDown(e) }, @@ -240,19 +241,6 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // Window events - function onWindowResize(container: HTMLElement) { - const viewports = Array.from(container.querySelectorAll("[data-viewport]")); - const boundsOfViewports = viewports.map((canvas) => { - const bounds = canvas.getBoundingClientRect(); - return [bounds.left, bounds.top, bounds.right, bounds.bottom]; - }); - - const flattened = boundsOfViewports.flat(); - const data = Float64Array.from(flattened); - - if (boundsOfViewports.length > 0) editor.handle.boundsOfViewports(data); - } - async function onBeforeUnload(e: BeforeUnloadEvent) { const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex]; if (activeDocument && !activeDocument.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id); @@ -390,7 +378,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // Bind the event listeners bindListeners(); // Resize on creation - onWindowResize(window.document.body); + updateBoundsOfViewports(editor, window.document.body); // Return the destructor return unbindListeners; diff --git a/frontend/src/state-providers/document.ts b/frontend/src/state-providers/document.ts index 2bdfd898..4aa982dd 100644 --- a/frontend/src/state-providers/document.ts +++ b/frontend/src/state-providers/document.ts @@ -5,7 +5,6 @@ import { type Editor } from "@graphite/wasm-communication/editor"; import { defaultWidgetLayout, patchWidgetLayout, - TriggerRefreshBoundsOfViewports, UpdateDocumentBarLayout, UpdateDocumentModeLayout, UpdateToolOptionsLayout, @@ -13,6 +12,7 @@ import { UpdateWorkingColorsLayout, UpdateNodeGraphBarLayout, TriggerGraphViewOverlay, + TriggerDelayedZoomCanvasToFitAll, } from "@graphite/wasm-communication/messages"; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type @@ -83,16 +83,6 @@ export function createDocumentState(editor: Editor) { }); }); - // Other - editor.subscriptions.subscribeJsMessage(TriggerRefreshBoundsOfViewports, async () => { - // Wait to display the unpopulated document panel (missing: tools, options bar content, scrollbar positioning, and canvas) - await tick(); - // Wait to display the populated document panel - await tick(); - - // Request a resize event so the viewport gets measured now that the canvas is populated and positioned correctly - window.dispatchEvent(new CustomEvent("resize")); - }); // Show or hide the graph view overlay editor.subscriptions.subscribeJsMessage(TriggerGraphViewOverlay, (triggerGraphViewOverlay) => { update((state) => { @@ -100,6 +90,9 @@ export function createDocumentState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(TriggerDelayedZoomCanvasToFitAll, () => { + setTimeout(() => editor.handle.zoomCanvasToFitAll(), 0); + }); return { subscribe, diff --git a/frontend/src/utility-functions/viewports.ts b/frontend/src/utility-functions/viewports.ts new file mode 100644 index 00000000..e73bcccf --- /dev/null +++ b/frontend/src/utility-functions/viewports.ts @@ -0,0 +1,14 @@ +import { type Editor } from "@graphite/wasm-communication/editor"; + +export function updateBoundsOfViewports(editor: Editor, container: HTMLElement) { + const viewports = Array.from(container.querySelectorAll("[data-viewport]")); + const boundsOfViewports = viewports.map((canvas) => { + const bounds = canvas.getBoundingClientRect(); + return [bounds.left, bounds.top, bounds.right, bounds.bottom]; + }); + + const flattened = boundsOfViewports.flat(); + const data = Float64Array.from(flattened); + + if (boundsOfViewports.length > 0) editor.handle.boundsOfViewports(data); +} diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 3c3cd11e..99005c73 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -649,6 +649,8 @@ export class TriggerCopyToClipboardBlobUrl extends JsMessage { readonly blobUrl!: string; } +export class TriggerDelayedZoomCanvasToFitAll extends JsMessage {} + export class TriggerDownloadBlobUrl extends JsMessage { readonly layerName!: string; @@ -672,8 +674,6 @@ export class TriggerDownloadTextFile extends JsMessage { readonly name!: string; } -export class TriggerRefreshBoundsOfViewports extends JsMessage {} - export class TriggerRevokeBlobUrl extends JsMessage { readonly url!: string; } @@ -782,8 +782,6 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage { readonly commitDate!: string; } -export class TriggerViewportResize extends JsMessage {} - // TODO: Eventually remove this (probably starting late 2024) export class TriggerUpgradeDocumentToVectorManipulationFormat extends JsMessage { readonly documentId!: bigint; @@ -1428,6 +1426,7 @@ export const messageMakers: Record = { DisplayRemoveEditableTextbox, TriggerAboutGraphiteLocalizedCommitDate, TriggerCopyToClipboardBlobUrl, + TriggerDelayedZoomCanvasToFitAll, TriggerFetchAndOpenDocument, TriggerDownloadBlobUrl, TriggerDownloadImage, @@ -1441,13 +1440,11 @@ export const messageMakers: Record = { TriggerLoadPreferences, TriggerOpenDocument, TriggerPaste, - TriggerRefreshBoundsOfViewports, TriggerRevokeBlobUrl, TriggerSavePreferences, TriggerTextCommit, TriggerTextCopy, TriggerUpgradeDocumentToVectorManipulationFormat, - TriggerViewportResize, TriggerVisitLink, UpdateActiveDocument, UpdateBox, diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 824e5f4c..3fea11c1 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -342,6 +342,13 @@ impl EditorHandle { self.dispatch(message); } + /// Zoom the canvas to fit all content + #[wasm_bindgen(js_name = zoomCanvasToFitAll)] + pub fn zoom_canvas_to_fit_all(&self) { + let message = DocumentMessage::ZoomCanvasToFitAll; + self.dispatch(message); + } + /// Mouse movement within the screenspace bounds of the viewport #[wasm_bindgen(js_name = onMouseMove)] pub fn on_mouse_move(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index 3d40e458..b9f4c12e 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -93,11 +93,17 @@ impl log::Log for WasmLog { log::Level::Error => (error, "error", "color:red"), }; - let file = record.file().unwrap_or_else(|| record.target()); - let line = record.line().map_or_else(|| "[Unknown]".to_string(), |line| line.to_string()); - let args = record.args(); - let msg = &format!("%c{name}\t{file}:{line}\n{args}"); // The %c is replaced by the message color - log(msg, color) + // The %c is replaced by the message color + if record.level() == log::Level::Info { + // We don't print the file name and line number for info-level logs because it's used for printing the message system logs + log(&format!("%c{}\t{}", name, record.args()), color); + } else { + let file = record.file().unwrap_or_else(|| record.target()); + let line = record.line().map_or_else(|| "[Unknown]".to_string(), |line| line.to_string()); + let args = record.args(); + + log(&format!("%c{name}\t{file}:{line}\n{args}"), color); + } } fn flush(&self) {}