diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 88925eb8fe..693530be1e 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -551,11 +551,11 @@ ;; Ruler guides: push the page guides to the render engine whenever they ;; change or their visibility toggles. When hidden we send an empty set. ;; While dragging, exclude the active guide so the SVG preview is the only line. - (mf/with-effect [@canvas-init? guides show-rulers? show-grids? @dragging-guide-id*] + (mf/with-effect [@canvas-init? guides objects show-rulers? show-grids? @dragging-guide-id*] (when @canvas-init? (let [guides (if (and show-rulers? show-grids?) (or guides {}) {}) guides (if-let [id @dragging-guide-id*] (dissoc guides id) guides)] - (wasm.api/set-guides guides)))) + (wasm.api/set-guides guides objects)))) (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 2cdfef8c92..1be4b54846 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -2244,13 +2244,14 @@ (defn set-guides "Serializes the page guides and sends them to the render engine. - `guides` is the page `:guides` map (id -> guide)." - [guides] + `guides` is the page `:guides` map (id -> guide); `objects` is the page + objects map, used to resolve each guide's board clip range." + [guides objects] (let [size (sr/get-guides-byte-size guides) offset (mem/alloc->offset-32 size) heapu32 (mem/get-heap-u32) heapf32 (mem/get-heap-f32)] - (sr/write-guides guides heapu32 heapf32 offset) + (sr/write-guides guides objects heapu32 heapf32 offset) (h/call wasm/internal-module "_set_guides") (request-render "set-guides"))) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index db2c62845d..7fa209a0a1 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -8,7 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] [app.common.types.color :as clr] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.render-wasm.serializers.color :as sr-clr] [app.render-wasm.wasm :as wasm] @@ -286,9 +288,11 @@ ;; --- Guides -;; Each guide is serialized as 3 x 32-bit words: -;; kind (u32) | color (u32 argb) | position (f32) -(def ^:private guide-entry-size 12) +;; Each guide is serialized as 5 x 32-bit words: +;; kind (u32) | color (u32 argb) | position (f32) | frame-start (f32) | frame-end (f32) +;; `frame-start`/`frame-end` hold the board clip range (along the guide's line +;; direction); they are NaN when the guide is not bound to a board. +(def ^:private guide-entry-size 20) ;; Default guide color used when a guide has no explicit color (matches the ;; previous SVG overlay `default-guide-color`). @@ -311,19 +315,37 @@ [guides] (+ 4 (* (count (or guides {})) guide-entry-size))) +(defn- guide-frame-range + "Returns the `[start end]` range of the board a guide belongs to, along the + guide's line direction (the board's y-range for vertical `:x` guides, its + x-range for horizontal `:y` guides). Returns nil for free guides and for + guides whose board is not a non-rotated root frame, matching the SVG + renderer's clip behavior." + [guide objects] + (when-let [frame (some->> (get guide :frame-id) (get objects))] + (when (and (cfh/root-frame? frame) + (not (ctst/rotated-frame? frame))) + (if (= :x (get guide :axis)) + [(:y frame) (+ (:y frame) (:height frame))] + [(:x frame) (+ (:x frame) (:width frame))])))) + (defn write-guides "Writes `guides` (a map id -> guide) into the heap views starting at the 32-bit `offset`. Layout: count header (u32) followed by - `kind | color | position` per guide." - [guides heapu32 heapf32 offset] + `kind | color | position | frame-start | frame-end` per guide. The frame + range is resolved from `objects` and written as NaN for free guides." + [guides objects heapu32 heapf32 offset] (let [guides (vec (vals (or guides {}))) total (count guides)] (aset heapu32 offset total) (loop [i 0] (when (< i total) (let [guide (nth guides i) - base (+ offset 1 (* i 3))] + base (+ offset 1 (* i 5)) + [frame-start frame-end] (guide-frame-range guide objects)] (aset heapu32 base (translate-guide-axis (get guide :axis))) (aset heapu32 (+ base 1) (sr-clr/hex->u32argb (or (get guide :color) default-guide-color) 1)) - (aset heapf32 (+ base 2) (get guide :position))) + (aset heapf32 (+ base 2) (get guide :position)) + (aset heapf32 (+ base 3) (or frame-start js/NaN)) + (aset heapf32 (+ base 4) (or frame-end js/NaN))) (recur (inc i)))))) diff --git a/render-wasm/src/render/ui.rs b/render-wasm/src/render/ui.rs index 38818b99ba..3b69955a82 100644 --- a/render-wasm/src/render/ui.rs +++ b/render-wasm/src/render/ui.rs @@ -67,7 +67,14 @@ pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) { rulers::render(canvas, viewbox, &render_state.fonts, &ruler_state); // TODO: pass guides data here let (horizontal, vertical) = get_ui_state().guides(); - guides::render(canvas, zoom, viewbox.area, horizontal, vertical); + guides::render( + canvas, + zoom, + render_state.options.dpr, + viewbox.area, + horizontal, + vertical, + ); canvas.restore(); diff --git a/render-wasm/src/render/ui/guides.rs b/render-wasm/src/render/ui/guides.rs index e72ee43da1..07bc5a6f8a 100644 --- a/render-wasm/src/render/ui/guides.rs +++ b/render-wasm/src/render/ui/guides.rs @@ -8,30 +8,57 @@ use crate::ui::{Guide, GuideKind}; pub fn render( canvas: &skia::Canvas, zoom: f32, + dpr: f32, area: Rect, horizontal: &[Guide], vertical: &[Guide], ) { for guide in horizontal { - render_guide(canvas, zoom, area, *guide); + render_guide(canvas, zoom, dpr, area, *guide); } for guide in vertical { - render_guide(canvas, zoom, area, *guide); + render_guide(canvas, zoom, dpr, area, *guide); } } -pub fn render_guide(canvas: &skia::Canvas, zoom: f32, area: Rect, guide: Guide) { +pub fn render_guide(canvas: &skia::Canvas, zoom: f32, dpr: f32, area: Rect, guide: Guide) { let mut paint = skia::Paint::default(); paint.set_style(skia::PaintStyle::Stroke); paint.set_color(Into::::into(guide.color)); - // we disable antialias and increase the stroke thickness so the guides - // do not appear faint or blurry. + paint.set_alpha((0.7 * 255.0) as u8); + paint.set_stroke_width(1.0 * dpr / zoom); + // we disable antialias so the guides do not appear faint or blurry. paint.set_anti_alias(false); - paint.set_stroke_width(2.0 / zoom); + + // The guide line spans the whole viewport, but when it belongs to a board + // the solid part is clipped to that board's range (along the line + // direction). The trimmed-out parts are not drawn here; the hover/drag + // dashed decorations are rendered by the SVG overlay instead. + let (full_start, full_end) = match guide.kind { + GuideKind::Vertical(_) => (area.top, area.bottom), + GuideKind::Horizontal(_) => (area.left, area.right), + }; + + let (start, end) = match guide.frame_range { + Some((frame_start, frame_end)) => { + let (lo, hi) = if frame_start <= frame_end { + (frame_start, frame_end) + } else { + (frame_end, frame_start) + }; + (lo.max(full_start), hi.min(full_end)) + } + None => (full_start, full_end), + }; + + // The clipped range can fall entirely outside the viewport. + if start > end { + return; + } let (x1, y1, x2, y2) = match guide.kind { - GuideKind::Vertical(x) => (x, area.top, x, area.bottom), - GuideKind::Horizontal(y) => (area.left, y, area.right, y), + GuideKind::Vertical(x) => (x, start, x, end), + GuideKind::Horizontal(y) => (start, y, end, y), }; canvas.draw_line((x1, y1), (x2, y2), &paint); diff --git a/render-wasm/src/state/ui.rs b/render-wasm/src/state/ui.rs index fadd6c3094..607dc3117b 100644 --- a/render-wasm/src/state/ui.rs +++ b/render-wasm/src/state/ui.rs @@ -109,11 +109,21 @@ mod tests { use crate::shapes::Color; fn vertical_guide(position: f32, index: usize) -> Guide { - Guide::new(GuideKind::Vertical(position), Color::BLACK, Some(index)) + Guide::new( + GuideKind::Vertical(position), + Color::BLACK, + Some(index), + None, + ) } fn horizontal_guide(position: f32, index: usize) -> Guide { - Guide::new(GuideKind::Horizontal(position), Color::BLACK, Some(index)) + Guide::new( + GuideKind::Horizontal(position), + Color::BLACK, + Some(index), + None, + ) } fn pool_with(guides: Vec) -> GuidePool { diff --git a/render-wasm/src/ui.rs b/render-wasm/src/ui.rs index effa1381c0..156b4a8748 100644 --- a/render-wasm/src/ui.rs +++ b/render-wasm/src/ui.rs @@ -29,14 +29,25 @@ pub struct Guide { pub color: Color, /// Index of the guide in the guide list (clojure side) pub index: usize, + /// When the guide belongs to a board, the `[start, end]` range (along the + /// guide's line direction) of that board. The guide is drawn solid only + /// within this range and trimmed outside it. `None` for free guides, which + /// span the whole viewport. + pub frame_range: Option<(f32, f32)>, } impl Guide { - pub fn new(kind: GuideKind, color: Color, index: Option) -> Self { + pub fn new( + kind: GuideKind, + color: Color, + index: Option, + frame_range: Option<(f32, f32)>, + ) -> Self { Self { kind, color, index: index.unwrap_or_default(), + frame_range, } } diff --git a/render-wasm/src/wasm/ui.rs b/render-wasm/src/wasm/ui.rs index 4c5ea47f4b..e71fd20388 100644 --- a/render-wasm/src/wasm/ui.rs +++ b/render-wasm/src/wasm/ui.rs @@ -29,6 +29,9 @@ impl From for RawGuideKind { /// /// The layout uses only 32-bit fields so it can be written from ClojureScript /// straight into the `HEAPU32`/`HEAPF32` views without padding surprises. +/// +/// `frame_start` / `frame_end` carry the board clip range (along the guide's +/// line direction). When the guide is not bound to a board they are `NaN`. #[repr(C)] #[repr(align(4))] #[derive(Debug, Clone, PartialEq, Copy)] @@ -36,6 +39,8 @@ pub struct RawGuide { kind: u32, color: u32, position: f32, + frame_start: f32, + frame_end: f32, } impl From for Guide { @@ -44,7 +49,12 @@ impl From for Guide { RawGuideKind::Vertical => GuideKind::Vertical(value.position), RawGuideKind::Horizontal => GuideKind::Horizontal(value.position), }; - Guide::new(kind, value.color.into(), None) + let frame_range = if value.frame_start.is_nan() || value.frame_end.is_nan() { + None + } else { + Some((value.frame_start, value.frame_end)) + }; + Guide::new(kind, value.color.into(), None, frame_range) } }