diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 5cfc6ad9..6bfd0dac 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -246,6 +246,8 @@ pub enum FrontendMessage { visible: bool, tilt: f64, flip: bool, + #[serde(rename = "selectionQuad")] + selection_quad: Option<[(f64, f64); 4]>, }, UpdateDocumentScrollbars { position: (f64, f64), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 7a0d4065..9cc930ff 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -838,6 +838,23 @@ impl MessageHandler> for DocumentMes let ruler_spacing = ruler_interval * ruler_scale; + // Compute the selection bounding box as 4 viewport-space corners preserving orientation + let selection_quad = if !self.graph_view_overlay_open { + self.network_interface + .selected_nodes() + .0 + .iter() + .filter(|node| self.network_interface.is_layer(node, &[])) + .filter_map(|layer| self.metadata().bounding_box_document(LayerNodeIdentifier::new(*layer, &self.network_interface))) + .reduce(Quad::combine_bounds) + .map(|[min, max]| { + let corners = [DVec2::new(min.x, min.y), DVec2::new(max.x, min.y), DVec2::new(max.x, max.y), DVec2::new(min.x, max.y)]; + corners.map(|c| document_to_viewport.transform_point2(c).into()) + }) + } else { + None + }; + responses.add(FrontendMessage::UpdateDocumentRulers { origin: ruler_origin.into(), spacing: ruler_spacing, @@ -845,6 +862,7 @@ impl MessageHandler> for DocumentMes visible: self.rulers_visible, tilt: if self.graph_view_overlay_open { 0. } else { current_ptz.tilt() }, flip: !self.graph_view_overlay_open && current_ptz.flip, + selection_quad, }); } DocumentMessage::RenderScrollbars => { diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index b93c6106..5d9e5b41 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -144,6 +144,8 @@ --color-data-invalid: #d6536e; // Same as --color-error-red --color-data-invalid-dim: #a7324a; + --color-overlay-blue: #00a8ff; + --color-none: white; --color-none-repeat: no-repeat; --color-none-position: center center; diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index b1a05186..94623d1b 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -47,6 +47,7 @@ let rulerTilt = 0; let rulerFlip = false; let rulerCursorPosition: { x: number; y: number } | undefined; + let rulerSelectionQuad: [number, number][] | undefined; let viewportBounds: DOMRect | undefined; // Rendered SVG viewport data @@ -291,13 +292,14 @@ scrollbarMultiplier = { x: multiplier[0], y: multiplier[1] }; } - export function updateDocumentRulers(origin: [number, number], spacing: number, interval: number, visible: boolean, tilt: number, flip: boolean) { + export function updateDocumentRulers(origin: [number, number], spacing: number, interval: number, visible: boolean, tilt: number, flip: boolean, selectionQuad: [number, number][] | undefined) { rulerOrigin = { x: origin[0], y: origin[1] }; rulerSpacing = spacing; rulerInterval = interval; rulersVisible = visible; rulerTilt = tilt; rulerFlip = flip; + rulerSelectionQuad = selectionQuad; } function updateRulerCursorPosition(e: PointerEvent) { @@ -498,8 +500,8 @@ subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => { await tick(); - const { origin, spacing, interval, visible, tilt, flip } = data; - updateDocumentRulers(origin, spacing, interval, visible, tilt, flip); + const { origin, spacing, interval, visible, tilt, flip, selectionQuad } = data; + updateDocumentRulers(origin, spacing, interval, visible, tilt, flip, selectionQuad || undefined); }); // Update mouse cursor icon @@ -615,6 +617,7 @@ numberInterval={rulerInterval} direction="Horizontal" cursorPosition={rulerCursorPosition} + selectionQuad={rulerSelectionQuad} bind:this={rulerHorizontal} /> @@ -631,6 +634,7 @@ numberInterval={rulerInterval} direction="Vertical" cursorPosition={rulerCursorPosition} + selectionQuad={rulerSelectionQuad} bind:this={rulerVertical} /> diff --git a/frontend/src/components/widgets/inputs/RulerInput.svelte b/frontend/src/components/widgets/inputs/RulerInput.svelte index b79bb560..428609ce 100644 --- a/frontend/src/components/widgets/inputs/RulerInput.svelte +++ b/frontend/src/components/widgets/inputs/RulerInput.svelte @@ -1,6 +1,7 @@ -
- - - {#each svgTexts as svgText} - {svgText.text} - {/each} - {#if cursorIndicatorPath} - - {/if} - +
+
+ + + {#each svgTexts as svgText} + {svgText.text} + {/each} + {#if cursorIndicatorPath} + + {/if} + +
+ {#if selectionExtent} + {@const isVertical = direction === "Vertical"} + {@const minPos = Math.round(selectionExtent.min)} + {@const maxPos = Math.round(selectionExtent.max)} + {@const half = Math.floor(SELECTION_ENDPOINT_SIZE / 2)} + {@const overlap = Math.ceil(SELECTION_ENDPOINT_SIZE / 2)} +
+
+ {#each [minPos, maxPos] as pos} +
+ {/each} +
+ {/if}