mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
🎉 Clip out outer board guide lines
This commit is contained in:
parent
b82d04e172
commit
c4d10db852
@ -551,11 +551,11 @@
|
|||||||
;; Ruler guides: push the page guides to the render engine whenever they
|
;; Ruler guides: push the page guides to the render engine whenever they
|
||||||
;; change or their visibility toggles. When hidden we send an empty set.
|
;; 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.
|
;; 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?
|
(when @canvas-init?
|
||||||
(let [guides (if (and show-rulers? show-grids?) (or guides {}) {})
|
(let [guides (if (and show-rulers? show-grids?) (or guides {}) {})
|
||||||
guides (if-let [id @dragging-guide-id*] (dissoc guides id) 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-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?)
|
||||||
(hooks/setup-viewport-size vport viewport-ref)
|
(hooks/setup-viewport-size vport viewport-ref)
|
||||||
|
|||||||
@ -2244,13 +2244,14 @@
|
|||||||
|
|
||||||
(defn set-guides
|
(defn set-guides
|
||||||
"Serializes the page guides and sends them to the render engine.
|
"Serializes the page guides and sends them to the render engine.
|
||||||
`guides` is the page `:guides` map (id -> guide)."
|
`guides` is the page `:guides` map (id -> guide); `objects` is the page
|
||||||
[guides]
|
objects map, used to resolve each guide's board clip range."
|
||||||
|
[guides objects]
|
||||||
(let [size (sr/get-guides-byte-size guides)
|
(let [size (sr/get-guides-byte-size guides)
|
||||||
offset (mem/alloc->offset-32 size)
|
offset (mem/alloc->offset-32 size)
|
||||||
heapu32 (mem/get-heap-u32)
|
heapu32 (mem/get-heap-u32)
|
||||||
heapf32 (mem/get-heap-f32)]
|
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")
|
(h/call wasm/internal-module "_set_guides")
|
||||||
(request-render "set-guides")))
|
(request-render "set-guides")))
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,9 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.types.color :as clr]
|
[app.common.types.color :as clr]
|
||||||
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.render-wasm.serializers.color :as sr-clr]
|
[app.render-wasm.serializers.color :as sr-clr]
|
||||||
[app.render-wasm.wasm :as wasm]
|
[app.render-wasm.wasm :as wasm]
|
||||||
@ -286,9 +288,11 @@
|
|||||||
|
|
||||||
;; --- Guides
|
;; --- Guides
|
||||||
|
|
||||||
;; Each guide is serialized as 3 x 32-bit words:
|
;; Each guide is serialized as 5 x 32-bit words:
|
||||||
;; kind (u32) | color (u32 argb) | position (f32)
|
;; kind (u32) | color (u32 argb) | position (f32) | frame-start (f32) | frame-end (f32)
|
||||||
(def ^:private guide-entry-size 12)
|
;; `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
|
;; Default guide color used when a guide has no explicit color (matches the
|
||||||
;; previous SVG overlay `default-guide-color`).
|
;; previous SVG overlay `default-guide-color`).
|
||||||
@ -311,19 +315,37 @@
|
|||||||
[guides]
|
[guides]
|
||||||
(+ 4 (* (count (or guides {})) guide-entry-size)))
|
(+ 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
|
(defn write-guides
|
||||||
"Writes `guides` (a map id -> guide) into the heap views starting at the
|
"Writes `guides` (a map id -> guide) into the heap views starting at the
|
||||||
32-bit `offset`. Layout: count header (u32) followed by
|
32-bit `offset`. Layout: count header (u32) followed by
|
||||||
`kind | color | position` per guide."
|
`kind | color | position | frame-start | frame-end` per guide. The frame
|
||||||
[guides heapu32 heapf32 offset]
|
range is resolved from `objects` and written as NaN for free guides."
|
||||||
|
[guides objects heapu32 heapf32 offset]
|
||||||
(let [guides (vec (vals (or guides {})))
|
(let [guides (vec (vals (or guides {})))
|
||||||
total (count guides)]
|
total (count guides)]
|
||||||
(aset heapu32 offset total)
|
(aset heapu32 offset total)
|
||||||
(loop [i 0]
|
(loop [i 0]
|
||||||
(when (< i total)
|
(when (< i total)
|
||||||
(let [guide (nth guides i)
|
(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 (translate-guide-axis (get guide :axis)))
|
||||||
(aset heapu32 (+ base 1) (sr-clr/hex->u32argb (or (get guide :color) default-guide-color) 1))
|
(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))))))
|
(recur (inc i))))))
|
||||||
|
|||||||
@ -67,7 +67,14 @@ pub fn render(render_state: &mut RenderState, shapes: ShapesPoolRef) {
|
|||||||
rulers::render(canvas, viewbox, &render_state.fonts, &ruler_state);
|
rulers::render(canvas, viewbox, &render_state.fonts, &ruler_state);
|
||||||
// TODO: pass guides data here
|
// TODO: pass guides data here
|
||||||
let (horizontal, vertical) = get_ui_state().guides();
|
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();
|
canvas.restore();
|
||||||
|
|
||||||
|
|||||||
@ -8,30 +8,57 @@ use crate::ui::{Guide, GuideKind};
|
|||||||
pub fn render(
|
pub fn render(
|
||||||
canvas: &skia::Canvas,
|
canvas: &skia::Canvas,
|
||||||
zoom: f32,
|
zoom: f32,
|
||||||
|
dpr: f32,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
horizontal: &[Guide],
|
horizontal: &[Guide],
|
||||||
vertical: &[Guide],
|
vertical: &[Guide],
|
||||||
) {
|
) {
|
||||||
for guide in horizontal {
|
for guide in horizontal {
|
||||||
render_guide(canvas, zoom, area, *guide);
|
render_guide(canvas, zoom, dpr, area, *guide);
|
||||||
}
|
}
|
||||||
for guide in vertical {
|
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();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
paint.set_color(Into::<skia::Color>::into(guide.color));
|
paint.set_color(Into::<skia::Color>::into(guide.color));
|
||||||
// we disable antialias and increase the stroke thickness so the guides
|
paint.set_alpha((0.7 * 255.0) as u8);
|
||||||
// do not appear faint or blurry.
|
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_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 {
|
let (x1, y1, x2, y2) = match guide.kind {
|
||||||
GuideKind::Vertical(x) => (x, area.top, x, area.bottom),
|
GuideKind::Vertical(x) => (x, start, x, end),
|
||||||
GuideKind::Horizontal(y) => (area.left, y, area.right, y),
|
GuideKind::Horizontal(y) => (start, y, end, y),
|
||||||
};
|
};
|
||||||
|
|
||||||
canvas.draw_line((x1, y1), (x2, y2), &paint);
|
canvas.draw_line((x1, y1), (x2, y2), &paint);
|
||||||
|
|||||||
@ -109,11 +109,21 @@ mod tests {
|
|||||||
use crate::shapes::Color;
|
use crate::shapes::Color;
|
||||||
|
|
||||||
fn vertical_guide(position: f32, index: usize) -> Guide {
|
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 {
|
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<Guide>) -> GuidePool {
|
fn pool_with(guides: Vec<Guide>) -> GuidePool {
|
||||||
|
|||||||
@ -29,14 +29,25 @@ pub struct Guide {
|
|||||||
pub color: Color,
|
pub color: Color,
|
||||||
/// Index of the guide in the guide list (clojure side)
|
/// Index of the guide in the guide list (clojure side)
|
||||||
pub index: usize,
|
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 {
|
impl Guide {
|
||||||
pub fn new(kind: GuideKind, color: Color, index: Option<usize>) -> Self {
|
pub fn new(
|
||||||
|
kind: GuideKind,
|
||||||
|
color: Color,
|
||||||
|
index: Option<usize>,
|
||||||
|
frame_range: Option<(f32, f32)>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind,
|
kind,
|
||||||
color,
|
color,
|
||||||
index: index.unwrap_or_default(),
|
index: index.unwrap_or_default(),
|
||||||
|
frame_range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,9 @@ impl From<u32> for RawGuideKind {
|
|||||||
///
|
///
|
||||||
/// The layout uses only 32-bit fields so it can be written from ClojureScript
|
/// The layout uses only 32-bit fields so it can be written from ClojureScript
|
||||||
/// straight into the `HEAPU32`/`HEAPF32` views without padding surprises.
|
/// 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(C)]
|
||||||
#[repr(align(4))]
|
#[repr(align(4))]
|
||||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||||
@ -36,6 +39,8 @@ pub struct RawGuide {
|
|||||||
kind: u32,
|
kind: u32,
|
||||||
color: u32,
|
color: u32,
|
||||||
position: f32,
|
position: f32,
|
||||||
|
frame_start: f32,
|
||||||
|
frame_end: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RawGuide> for Guide {
|
impl From<RawGuide> for Guide {
|
||||||
@ -44,7 +49,12 @@ impl From<RawGuide> for Guide {
|
|||||||
RawGuideKind::Vertical => GuideKind::Vertical(value.position),
|
RawGuideKind::Vertical => GuideKind::Vertical(value.position),
|
||||||
RawGuideKind::Horizontal => GuideKind::Horizontal(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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user