mirror of
https://github.com/penpot/penpot.git
synced 2025-05-17 07:26:10 +02:00
✨ Add support for WASM transforms
This commit is contained in:
parent
a3a757f842
commit
1bb337c3dd
18 changed files with 658 additions and 153 deletions
|
@ -95,6 +95,11 @@
|
||||||
(impl/getUnsignedParts (.-uuid ^UUID this))))
|
(impl/getUnsignedParts (.-uuid ^UUID this))))
|
||||||
|
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn from-unsigned-parts
|
||||||
|
[a b c d]
|
||||||
|
(uuid (impl/fromUnsignedParts a b c d))))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn get-u32
|
(defn get-u32
|
||||||
"A cached variant of get-unsigned-parts"
|
"A cached variant of get-unsigned-parts"
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
[app.main.data.workspace.guides :as-alias dwg]
|
[app.main.data.workspace.guides :as-alias dwg]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
|
[app.main.features :as features]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
@ -160,8 +162,13 @@
|
||||||
change-to-fixed?
|
change-to-fixed?
|
||||||
(assoc :grow-type :fixed))))
|
(assoc :grow-type :fixed))))
|
||||||
|
|
||||||
(defn- clear-local-transform []
|
(defn clear-local-transform []
|
||||||
(ptk/reify ::clear-local-transform
|
(ptk/reify ::clear-local-transform
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ state _]
|
||||||
|
(when (features/active-feature? state "render-wasm/v1")
|
||||||
|
(wasm.api/set-modifiers nil)))
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(-> state
|
||||||
|
@ -391,6 +398,7 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update state :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree)))))
|
(update state :workspace-modifiers calculate-update-modifiers state ignore-constraints ignore-snap-pixel modif-tree)))))
|
||||||
|
|
||||||
|
|
||||||
(defn set-modifiers
|
(defn set-modifiers
|
||||||
([modif-tree]
|
([modif-tree]
|
||||||
(set-modifiers modif-tree false))
|
(set-modifiers modif-tree false))
|
||||||
|
@ -409,6 +417,37 @@
|
||||||
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
|
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
|
||||||
(assoc state :workspace-modifiers modifiers))))))
|
(assoc state :workspace-modifiers modifiers))))))
|
||||||
|
|
||||||
|
(defn set-wasm-modifiers
|
||||||
|
([modif-tree]
|
||||||
|
(set-wasm-modifiers modif-tree false))
|
||||||
|
|
||||||
|
([modif-tree ignore-constraints]
|
||||||
|
(set-wasm-modifiers modif-tree ignore-constraints false))
|
||||||
|
|
||||||
|
([modif-tree ignore-constraints ignore-snap-pixel]
|
||||||
|
(set-wasm-modifiers modif-tree ignore-constraints ignore-snap-pixel nil))
|
||||||
|
|
||||||
|
([modif-tree _ignore-constraints _ignore-snap-pixel _params]
|
||||||
|
(ptk/reify ::set-wasm-modifiers
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ _ _]
|
||||||
|
(let [entries
|
||||||
|
(->> modif-tree
|
||||||
|
(mapv (fn [[id data]]
|
||||||
|
{:id id
|
||||||
|
:transform (ctm/modifiers->transform (:modifiers data))})))
|
||||||
|
|
||||||
|
modifiers-new
|
||||||
|
(wasm.api/propagate-modifiers entries)]
|
||||||
|
(wasm.api/set-modifiers modifiers-new))))))
|
||||||
|
|
||||||
|
(defn set-selrect-transform
|
||||||
|
[modifiers]
|
||||||
|
(ptk/reify ::set-selrect-transform
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc state :workspace-selrect-transform (ctm/modifiers->transform modifiers)))))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
xf-rotation-shape
|
xf-rotation-shape
|
||||||
(comp
|
(comp
|
||||||
|
@ -418,6 +457,33 @@
|
||||||
|
|
||||||
;; Rotation use different algorithm to calculate children
|
;; Rotation use different algorithm to calculate children
|
||||||
;; modifiers (and do not use child constraints).
|
;; modifiers (and do not use child constraints).
|
||||||
|
(defn set-wasm-rotation-modifiers
|
||||||
|
([angle shapes]
|
||||||
|
(set-wasm-rotation-modifiers angle shapes (-> shapes gsh/shapes->rect grc/rect->center)))
|
||||||
|
|
||||||
|
([angle shapes center]
|
||||||
|
(ptk/reify ::set-wasm-rotation-modifiers
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ state _]
|
||||||
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
|
ids (sequence xf-rotation-shape shapes)
|
||||||
|
|
||||||
|
get-modifier
|
||||||
|
(fn [shape]
|
||||||
|
(ctm/rotation-modifiers shape center angle))
|
||||||
|
|
||||||
|
modif-tree
|
||||||
|
(-> (build-modif-tree ids objects get-modifier)
|
||||||
|
(gm/set-objects-modifiers objects))
|
||||||
|
|
||||||
|
modifiers
|
||||||
|
(->> modif-tree
|
||||||
|
(map (fn [[id {:keys [modifiers]}]]
|
||||||
|
{:id id
|
||||||
|
:transform (ctm/modifiers->transform modifiers)})))]
|
||||||
|
|
||||||
|
(wasm.api/set-modifiers modifiers))))))
|
||||||
|
|
||||||
(defn set-rotation-modifiers
|
(defn set-rotation-modifiers
|
||||||
([angle shapes]
|
([angle shapes]
|
||||||
(set-rotation-modifiers angle shapes (-> shapes gsh/shapes->rect grc/rect->center)))
|
(set-rotation-modifiers angle shapes (-> shapes gsh/shapes->rect grc/rect->center)))
|
||||||
|
@ -575,3 +641,4 @@
|
||||||
(if undo-transation?
|
(if undo-transation?
|
||||||
(rx/of (dwu/commit-undo-transaction undo-id))
|
(rx/of (dwu/commit-undo-transaction undo-id))
|
||||||
(rx/empty))))))))
|
(rx/empty))))))))
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
[app.main.data.workspace.modifiers :as dwm]
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.snap :as snap]
|
[app.main.snap :as snap]
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[app.util.array :as array]
|
[app.util.array :as array]
|
||||||
|
@ -132,7 +133,9 @@
|
||||||
(ptk/reify ::finish-transform
|
(ptk/reify ::finish-transform
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(update state :workspace-local dissoc :transform :duplicate-move-started? false))))
|
(-> state
|
||||||
|
(update :workspace-local dissoc :transform :duplicate-move-started?)
|
||||||
|
(dissoc :workspace-selrect-transform)))))
|
||||||
|
|
||||||
;; -- Resize --------------------------------------------------------
|
;; -- Resize --------------------------------------------------------
|
||||||
|
|
||||||
|
@ -217,27 +220,23 @@
|
||||||
(not (mth/close? (dm/get-prop scalev :x) 1))
|
(not (mth/close? (dm/get-prop scalev :x) 1))
|
||||||
|
|
||||||
set-fix-height?
|
set-fix-height?
|
||||||
(not (mth/close? (dm/get-prop scalev :y) 1))
|
(not (mth/close? (dm/get-prop scalev :y) 1))]
|
||||||
|
|
||||||
modifiers (cond-> (ctm/empty)
|
(cond-> (ctm/empty)
|
||||||
(some? displacement)
|
(some? displacement)
|
||||||
(ctm/move displacement)
|
(ctm/move displacement)
|
||||||
|
|
||||||
:always
|
:always
|
||||||
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
(ctm/resize scalev resize-origin shape-transform shape-transform-inverse)
|
||||||
|
|
||||||
^boolean set-fix-width?
|
^boolean set-fix-width?
|
||||||
(ctm/change-property :layout-item-h-sizing :fix)
|
(ctm/change-property :layout-item-h-sizing :fix)
|
||||||
|
|
||||||
^boolean set-fix-height?
|
^boolean set-fix-height?
|
||||||
(ctm/change-property :layout-item-v-sizing :fix)
|
(ctm/change-property :layout-item-v-sizing :fix)
|
||||||
|
|
||||||
^boolean scale-text
|
^boolean scale-text
|
||||||
(ctm/scale-content (dm/get-prop scalev :x)))
|
(ctm/scale-content (dm/get-prop scalev :x)))))
|
||||||
|
|
||||||
modif-tree (dwm/create-modif-tree ids modifiers)]
|
|
||||||
|
|
||||||
(rx/of (dwm/set-modifiers modif-tree scale-text))))
|
|
||||||
|
|
||||||
;; Unifies the instantaneous proportion lock modifier
|
;; Unifies the instantaneous proportion lock modifier
|
||||||
;; activated by Shift key and the shapes own proportion
|
;; activated by Shift key and the shapes own proportion
|
||||||
|
@ -264,18 +263,43 @@
|
||||||
focus (:workspace-focus-selected state)
|
focus (:workspace-focus-selected state)
|
||||||
zoom (dm/get-in state [:workspace-local :zoom] 1)
|
zoom (dm/get-in state [:workspace-local :zoom] 1)
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
shapes (map (d/getf objects) ids)]
|
shapes (map (d/getf objects) ids)
|
||||||
|
|
||||||
|
resize-events-stream
|
||||||
|
(->> ms/mouse-position
|
||||||
|
(rx/filter some?)
|
||||||
|
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
|
||||||
|
(rx/map normalize-proportion-lock)
|
||||||
|
(rx/switch-map
|
||||||
|
(fn [[point _ _ :as current]]
|
||||||
|
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
||||||
|
(rx/map #(conj current %)))))
|
||||||
|
(rx/map #(resize shape initial-position layout %))
|
||||||
|
(rx/share))]
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> ms/mouse-position
|
(rx/merge
|
||||||
(rx/filter some?)
|
(->> resize-events-stream
|
||||||
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
|
(rx/mapcat
|
||||||
(rx/map normalize-proportion-lock)
|
(fn [modifiers]
|
||||||
(rx/switch-map (fn [[point _ _ :as current]]
|
(let [modif-tree (dwm/create-modif-tree ids modifiers)]
|
||||||
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
(rx/map #(conj current %)))))
|
(rx/of
|
||||||
(rx/mapcat (partial resize shape initial-position layout))
|
(dwm/set-selrect-transform modifiers)
|
||||||
(rx/take-until stopper))
|
(dwm/set-wasm-modifiers modif-tree (contains? layout :scale-text)))
|
||||||
|
|
||||||
|
(rx/of (dwm/set-modifiers modif-tree (contains? layout :scale-text)))))))
|
||||||
|
(rx/take-until stopper))
|
||||||
|
|
||||||
|
;; The last event we need to use the old method so the elements are correctly positioned until
|
||||||
|
;; all the logic is implemented in wasm
|
||||||
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
|
(->> resize-events-stream
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/last)
|
||||||
|
(rx/map #(dwm/set-modifiers (dwm/create-modif-tree ids %) (contains? layout :scale-text))))
|
||||||
|
(rx/empty)))
|
||||||
|
|
||||||
(rx/of (dwm/apply-modifiers)
|
(rx/of (dwm/apply-modifiers)
|
||||||
(finish-transform))))))))
|
(finish-transform))))))))
|
||||||
|
|
||||||
|
@ -371,7 +395,7 @@
|
||||||
(assoc-in [:workspace-local :transform] :rotate)))
|
(assoc-in [:workspace-local :transform] :rotate)))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ stream]
|
(watch [_ state stream]
|
||||||
(let [stopper (mse/drag-stopper stream)
|
(let [stopper (mse/drag-stopper stream)
|
||||||
group (gsh/shapes->rect shapes)
|
group (gsh/shapes->rect shapes)
|
||||||
group-center (grc/rect->center group)
|
group-center (grc/rect->center group)
|
||||||
|
@ -390,15 +414,29 @@
|
||||||
angle (if shift?
|
angle (if shift?
|
||||||
(* (mth/floor (/ angle 15)) 15)
|
(* (mth/floor (/ angle 15)) 15)
|
||||||
angle)]
|
angle)]
|
||||||
angle))]
|
angle))
|
||||||
|
|
||||||
|
angle-stream
|
||||||
|
(->> ms/mouse-position
|
||||||
|
(rx/with-latest-from ms/mouse-position-mod ms/mouse-position-shift)
|
||||||
|
(rx/map
|
||||||
|
(fn [[pos mod? shift?]]
|
||||||
|
(calculate-angle pos mod? shift?)))
|
||||||
|
(rx/share))]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> ms/mouse-position
|
(rx/merge
|
||||||
(rx/with-latest-from ms/mouse-position-mod ms/mouse-position-shift)
|
(->> angle-stream
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [[pos mod? shift?]]
|
#(if (features/active-feature? state "render-wasm/v1")
|
||||||
(let [delta-angle (calculate-angle pos mod? shift?)]
|
(dwm/set-wasm-rotation-modifiers % shapes group-center)
|
||||||
(dwm/set-rotation-modifiers delta-angle shapes group-center))))
|
(dwm/set-rotation-modifiers % shapes group-center)))
|
||||||
(rx/take-until stopper))
|
(rx/take-until stopper))
|
||||||
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
|
(->> angle-stream
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/last)
|
||||||
|
(rx/map #(dwm/set-rotation-modifiers % shapes group-center)))
|
||||||
|
(rx/empty)))
|
||||||
(rx/of (dwm/apply-modifiers)
|
(rx/of (dwm/apply-modifiers)
|
||||||
(finish-transform)))))))
|
(finish-transform)))))))
|
||||||
|
|
||||||
|
@ -536,16 +574,17 @@
|
||||||
position (->> ms/mouse-position
|
position (->> ms/mouse-position
|
||||||
(rx/map #(gpt/to-vec from-position %)))
|
(rx/map #(gpt/to-vec from-position %)))
|
||||||
|
|
||||||
snap-delta (rx/concat
|
snap-delta
|
||||||
;; We send the nil first so the stream is not waiting for the first value
|
(rx/concat
|
||||||
(rx/of nil)
|
;; We send the nil first so the stream is not waiting for the first value
|
||||||
(->> position
|
(rx/of nil)
|
||||||
;; FIXME: performance throttle
|
(->> position
|
||||||
(rx/throttle 20)
|
;; FIXME: performance throttle
|
||||||
(rx/switch-map
|
(rx/throttle 20)
|
||||||
(fn [pos]
|
(rx/switch-map
|
||||||
(->> (snap/closest-snap-move page-id shapes objects layout zoom focus pos)
|
(fn [pos]
|
||||||
(rx/map #(array pos %)))))))]
|
(->> (snap/closest-snap-move page-id shapes objects layout zoom focus pos)
|
||||||
|
(rx/map #(array pos %)))))))]
|
||||||
(if (empty? shapes)
|
(if (empty? shapes)
|
||||||
(rx/of (finish-transform))
|
(rx/of (finish-transform))
|
||||||
(let [move-stream
|
(let [move-stream
|
||||||
|
@ -570,29 +609,45 @@
|
||||||
cell-data (when (and grid-layout? (not mod?)) (gslg/get-drop-cell target-frame objects position))]
|
cell-data (when (and grid-layout? (not mod?)) (gslg/get-drop-cell target-frame objects position))]
|
||||||
(array move-vector target-frame drop-index cell-data))))
|
(array move-vector target-frame drop-index cell-data))))
|
||||||
|
|
||||||
(rx/take-until stopper))]
|
(rx/take-until stopper))
|
||||||
|
|
||||||
|
modifiers-stream
|
||||||
|
(->> move-stream
|
||||||
|
(rx/with-latest-from array/conj ms/mouse-position-shift)
|
||||||
|
(rx/map
|
||||||
|
(fn [[move-vector target-frame drop-index cell-data shift?]]
|
||||||
|
(let [x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector)))
|
||||||
|
[move-vector snap-ignore-axis]
|
||||||
|
(cond
|
||||||
|
(and shift? x-disp?)
|
||||||
|
[(assoc move-vector :y 0) :y]
|
||||||
|
|
||||||
|
shift?
|
||||||
|
[(assoc move-vector :x 0) :x]
|
||||||
|
|
||||||
|
:else
|
||||||
|
[move-vector nil])]
|
||||||
|
[(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
|
||||||
|
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data))
|
||||||
|
snap-ignore-axis])))
|
||||||
|
(rx/share))]
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
;; Temporary modifiers stream
|
;; Temporary modifiers stream
|
||||||
(->> move-stream
|
(->> modifiers-stream
|
||||||
(rx/with-latest-from array/conj ms/mouse-position-shift)
|
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [[move-vector target-frame drop-index cell-data shift?]]
|
(fn [[modifiers snap-ignore-axis]]
|
||||||
(let [x-disp? (> (mth/abs (:x move-vector)) (mth/abs (:y move-vector)))
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
[move-vector snap-ignore-axis]
|
(dwm/set-wasm-modifiers modifiers false false {:snap-ignore-axis snap-ignore-axis})
|
||||||
(cond
|
(dwm/set-modifiers modifiers false false {:snap-ignore-axis snap-ignore-axis})))))
|
||||||
(and shift? x-disp?)
|
|
||||||
[(assoc move-vector :y 0) :y]
|
|
||||||
|
|
||||||
shift?
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
[(assoc move-vector :x 0) :x]
|
(->> modifiers-stream
|
||||||
|
(rx/last)
|
||||||
:else
|
(rx/map
|
||||||
[move-vector nil])]
|
(fn [[modifiers snap-ignore-axis]]
|
||||||
|
(dwm/set-modifiers modifiers false false {:snap-ignore-axis snap-ignore-axis}))))
|
||||||
(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
|
(rx/empty))
|
||||||
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data)
|
|
||||||
(dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis}))))))
|
|
||||||
|
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
(rx/with-latest-from ms/mouse-position-alt)
|
(rx/with-latest-from ms/mouse-position-alt)
|
||||||
|
|
|
@ -125,6 +125,9 @@
|
||||||
(def workspace-drawing
|
(def workspace-drawing
|
||||||
(l/derived :workspace-drawing st/state))
|
(l/derived :workspace-drawing st/state))
|
||||||
|
|
||||||
|
(def workspace-selrect-transform
|
||||||
|
(l/derived :workspace-selrect-transform st/state))
|
||||||
|
|
||||||
;; TODO: rename to workspace-selected (?)
|
;; TODO: rename to workspace-selected (?)
|
||||||
;; Don't use directly from components, this is a proxy to improve performance of selected-shapes
|
;; Don't use directly from components, this is a proxy to improve performance of selected-shapes
|
||||||
(def ^:private selected-shapes-data
|
(def ^:private selected-shapes-data
|
||||||
|
|
|
@ -318,7 +318,13 @@
|
||||||
[{:keys [shape zoom color on-move-selected on-context-menu disable-handlers]}]
|
[{:keys [shape zoom color on-move-selected on-context-menu disable-handlers]}]
|
||||||
(let [selrect (dm/get-prop shape :selrect)
|
(let [selrect (dm/get-prop shape :selrect)
|
||||||
transform-type (mf/deref refs/current-transform)
|
transform-type (mf/deref refs/current-transform)
|
||||||
transform (gsh/transform-str shape)]
|
sr-transform (mf/deref refs/workspace-selrect-transform)
|
||||||
|
|
||||||
|
transform
|
||||||
|
(dm/str
|
||||||
|
(cond->> (gsh/transform-matrix shape)
|
||||||
|
(some? sr-transform)
|
||||||
|
(gmt/multiply sr-transform)))]
|
||||||
|
|
||||||
(when (and (some? selrect)
|
(when (and (some? selrect)
|
||||||
(not (or (= transform-type :move)
|
(not (or (= transform-type :move)
|
||||||
|
@ -336,13 +342,18 @@
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[{:keys [shape zoom color on-resize on-rotate disable-handlers]}]
|
[{:keys [shape zoom color on-resize on-rotate disable-handlers]}]
|
||||||
(let [transform-type (mf/deref refs/current-transform)
|
(let [transform-type (mf/deref refs/current-transform)
|
||||||
|
sr-transform (mf/deref refs/workspace-selrect-transform)
|
||||||
|
|
||||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||||
|
|
||||||
layout (mf/deref refs/workspace-layout)
|
layout (mf/deref refs/workspace-layout)
|
||||||
scale-text? (contains? layout :scale-text)
|
scale-text? (contains? layout :scale-text)
|
||||||
|
|
||||||
selrect (dm/get-prop shape :selrect)
|
selrect (dm/get-prop shape :selrect)
|
||||||
transform (gsh/transform-matrix shape)
|
|
||||||
|
transform (cond->> (gsh/transform-matrix shape)
|
||||||
|
(some? sr-transform)
|
||||||
|
(gmt/multiply sr-transform))
|
||||||
|
|
||||||
rotation (-> (gpt/point 1 0)
|
rotation (-> (gpt/point 1 0)
|
||||||
(gpt/transform (:transform shape))
|
(gpt/transform (:transform shape))
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.types.shape :as cts]
|
|
||||||
[app.common.types.shape-tree :as ctt]
|
[app.common.types.shape-tree :as ctt]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.main.data.workspace.modifiers :as dwm]
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
|
@ -112,9 +111,7 @@
|
||||||
text-modifiers (mf/deref refs/workspace-text-modifier)
|
text-modifiers (mf/deref refs/workspace-text-modifier)
|
||||||
|
|
||||||
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
||||||
(binding [cts/*wasm-sync* true]
|
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers))
|
||||||
(-> (into selected (keys modifiers))
|
|
||||||
(apply-modifiers-to-selected base-objects text-modifiers modifiers))))
|
|
||||||
|
|
||||||
selected-shapes (keep (d/getf objects-modified) selected)
|
selected-shapes (keep (d/getf objects-modified) selected)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
(:require
|
(:require
|
||||||
["react-dom/server" :as rds]
|
["react-dom/server" :as rds]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.svg.path :as path]
|
[app.common.svg.path :as path]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
@ -563,12 +564,107 @@
|
||||||
(rx/reduce conj [])
|
(rx/reduce conj [])
|
||||||
(rx/subs! request-render)))))
|
(rx/subs! request-render)))))
|
||||||
|
|
||||||
|
(defn uuid->u8
|
||||||
|
[id]
|
||||||
|
(let [buffer (uuid/get-u32 id)
|
||||||
|
u32-arr (js/Uint32Array. 4)]
|
||||||
|
(doseq [i (range 0 4)]
|
||||||
|
(aset u32-arr i (aget buffer i)))
|
||||||
|
(js/Uint8Array. (.-buffer u32-arr))))
|
||||||
|
|
||||||
|
(defn matrix->u8
|
||||||
|
[{:keys [a b c d e f]}]
|
||||||
|
(let [f32-arr (js/Float32Array. 6)]
|
||||||
|
(aset f32-arr 0 a)
|
||||||
|
(aset f32-arr 1 b)
|
||||||
|
(aset f32-arr 2 c)
|
||||||
|
(aset f32-arr 3 d)
|
||||||
|
(aset f32-arr 4 e)
|
||||||
|
(aset f32-arr 5 f)
|
||||||
|
(js/Uint8Array. (.-buffer f32-arr))))
|
||||||
|
|
||||||
|
(defn data->entry
|
||||||
|
[data offset]
|
||||||
|
(let [id1 (.getUint32 data (+ offset 0) true)
|
||||||
|
id2 (.getUint32 data (+ offset 4) true)
|
||||||
|
id3 (.getUint32 data (+ offset 8) true)
|
||||||
|
id4 (.getUint32 data (+ offset 12) true)
|
||||||
|
|
||||||
|
a (.getFloat32 data (+ offset 16) true)
|
||||||
|
b (.getFloat32 data (+ offset 20) true)
|
||||||
|
c (.getFloat32 data (+ offset 24) true)
|
||||||
|
d (.getFloat32 data (+ offset 28) true)
|
||||||
|
e (.getFloat32 data (+ offset 32) true)
|
||||||
|
f (.getFloat32 data (+ offset 36) true)
|
||||||
|
|
||||||
|
id (uuid/from-unsigned-parts id1 id2 id3 id4)]
|
||||||
|
|
||||||
|
{:id id
|
||||||
|
:transform (gmt/matrix a b c d e f)}))
|
||||||
|
|
||||||
|
(defn propagate-modifiers
|
||||||
|
[entries]
|
||||||
|
(let [entry-size 40
|
||||||
|
ptr (h/call internal-module "_alloc_bytes" (* entry-size (count entries)))
|
||||||
|
|
||||||
|
heap
|
||||||
|
(js/Uint8Array.
|
||||||
|
(.-buffer (gobj/get ^js internal-module "HEAPU8"))
|
||||||
|
ptr
|
||||||
|
(* entry-size (count entries)))]
|
||||||
|
|
||||||
|
(loop [entries (seq entries)
|
||||||
|
offset 0]
|
||||||
|
(when-not (empty? entries)
|
||||||
|
(let [{:keys [id transform]} (first entries)]
|
||||||
|
(.set heap (uuid->u8 id) offset)
|
||||||
|
(.set heap (matrix->u8 transform) (+ offset 16))
|
||||||
|
(recur (rest entries) (+ offset entry-size)))))
|
||||||
|
|
||||||
|
(let [result-ptr (h/call internal-module "_propagate_modifiers")
|
||||||
|
heap (js/DataView. (.-buffer (gobj/get ^js internal-module "HEAPU8")))
|
||||||
|
len (.getUint32 heap result-ptr true)
|
||||||
|
result
|
||||||
|
(->> (range 0 len)
|
||||||
|
(mapv #(data->entry heap (+ result-ptr 4 (* % entry-size)))))]
|
||||||
|
(h/call internal-module "_free_bytes")
|
||||||
|
|
||||||
|
result)))
|
||||||
|
|
||||||
(defn set-canvas-background
|
(defn set-canvas-background
|
||||||
[background]
|
[background]
|
||||||
(let [rgba (rgba-from-hex background 1)]
|
(let [rgba (rgba-from-hex background 1)]
|
||||||
(h/call internal-module "_set_canvas_background" rgba)
|
(h/call internal-module "_set_canvas_background" rgba)
|
||||||
(request-render "set-canvas-background")))
|
(request-render "set-canvas-background")))
|
||||||
|
|
||||||
|
(defn set-modifiers
|
||||||
|
[modifiers]
|
||||||
|
(if (empty? modifiers)
|
||||||
|
(h/call internal-module "_clean_modifiers")
|
||||||
|
|
||||||
|
(let [ENTRY_SIZE 40
|
||||||
|
|
||||||
|
ptr
|
||||||
|
(h/call internal-module "_alloc_bytes" (* ENTRY_SIZE (count modifiers)))
|
||||||
|
|
||||||
|
heap
|
||||||
|
(js/Uint8Array.
|
||||||
|
(.-buffer (gobj/get ^js internal-module "HEAPU8"))
|
||||||
|
ptr
|
||||||
|
(* ENTRY_SIZE (count modifiers)))]
|
||||||
|
|
||||||
|
(loop [entries (seq modifiers)
|
||||||
|
offset 0]
|
||||||
|
(when-not (empty? entries)
|
||||||
|
(let [{:keys [id transform]} (first entries)]
|
||||||
|
(.set heap (uuid->u8 id) offset)
|
||||||
|
(.set heap (matrix->u8 transform) (+ offset 16))
|
||||||
|
(recur (rest entries) (+ offset ENTRY_SIZE)))))
|
||||||
|
|
||||||
|
(h/call internal-module "_set_modifiers")
|
||||||
|
|
||||||
|
(request-render "set-modifiers"))))
|
||||||
|
|
||||||
(defn initialize
|
(defn initialize
|
||||||
[base-objects zoom vbox background]
|
[base-objects zoom vbox background]
|
||||||
(let [rgba (rgba-from-hex background 1)]
|
(let [rgba (rgba-from-hex background 1)]
|
||||||
|
|
|
@ -6,7 +6,7 @@ else
|
||||||
export _BUILD_MODE=${1:-debug};
|
export _BUILD_MODE=${1:-debug};
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export EMCC_CFLAGS="--no-entry \
|
EMCC_CFLAGS="--no-entry \
|
||||||
-Os \
|
-Os \
|
||||||
-sASSERTIONS=1 \
|
-sASSERTIONS=1 \
|
||||||
-sALLOW_TABLE_GROWTH=1 \
|
-sALLOW_TABLE_GROWTH=1 \
|
||||||
|
@ -28,8 +28,11 @@ _CARGO_PARAMS="--target=wasm32-unknown-emscripten";
|
||||||
|
|
||||||
if [ "$_BUILD_MODE" = "release" ]; then
|
if [ "$_BUILD_MODE" = "release" ]; then
|
||||||
_CARGO_PARAMS="--release $_CARGO_PARAMS"
|
_CARGO_PARAMS="--release $_CARGO_PARAMS"
|
||||||
|
else
|
||||||
|
EMCC_CFLAGS="$EMCC_CFLAGS -sMALLOC=emmalloc-debug"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export EMCC_CFLAGS;
|
||||||
export _CARGO_PARAMS;
|
export _CARGO_PARAMS;
|
||||||
|
|
||||||
export SKIA_BINARIES_URL="https://github.com/penpot/skia-binaries/releases/download/0.80.1-1/skia-binaries-9e7d2684a17084095aef-wasm32-unknown-emscripten-gl-svg-textlayout.tar.gz"
|
export SKIA_BINARIES_URL="https://github.com/penpot/skia-binaries/releases/download/0.80.1-1/skia-binaries-9e7d2684a17084095aef-wasm32-unknown-emscripten-gl-svg-textlayout.tar.gz"
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
mod math;
|
mod math;
|
||||||
|
mod matrix;
|
||||||
mod mem;
|
mod mem;
|
||||||
mod render;
|
mod render;
|
||||||
mod shapes;
|
mod shapes;
|
||||||
|
@ -9,8 +13,8 @@ mod state;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
use crate::shapes::{BoolType, Kind, Path};
|
use crate::mem::SerializableResult;
|
||||||
|
use crate::shapes::{BoolType, Kind, Path, TransformEntry};
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use crate::utils::uuid_from_u32_quartet;
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
|
|
||||||
|
@ -590,6 +594,67 @@ pub extern "C" fn set_shape_path_attrs(num_attrs: u32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn propagate_modifiers() -> *mut u8 {
|
||||||
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
|
let mut entries: Vec<_> = bytes
|
||||||
|
.chunks(size_of::<TransformEntry>())
|
||||||
|
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
|
let mut processed = HashSet::<Uuid>::new();
|
||||||
|
|
||||||
|
let mut result = Vec::<TransformEntry>::new();
|
||||||
|
|
||||||
|
// Propagate the transform to children
|
||||||
|
while let Some(entry) = entries.pop() {
|
||||||
|
if !processed.contains(&entry.id) {
|
||||||
|
if let Some(shape) = state.shapes.get(&entry.id) {
|
||||||
|
let mut children: Vec<TransformEntry> = shape
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|id| TransformEntry {
|
||||||
|
id: id.clone(),
|
||||||
|
transform: entry.transform,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
entries.append(&mut children);
|
||||||
|
|
||||||
|
processed.insert(entry.id);
|
||||||
|
result.push(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mem::write_vec(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn clean_modifiers() {
|
||||||
|
if let Some(state) = unsafe { STATE.as_mut() } {
|
||||||
|
state.modifiers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn set_modifiers() {
|
||||||
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
|
let entries: Vec<_> = bytes
|
||||||
|
.chunks(size_of::<TransformEntry>())
|
||||||
|
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
state.modifiers.insert(entry.id, entry.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn add_shape_shadow(
|
pub extern "C" fn add_shape_shadow(
|
||||||
raw_color: u32,
|
raw_color: u32,
|
||||||
|
|
121
render-wasm/src/matrix.rs
Normal file
121
render-wasm/src/matrix.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// allowing dead code so we can have some API's that are not used yet without warnings
|
||||||
|
#![allow(dead_code)]
|
||||||
|
use skia_safe as skia;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Matrix {
|
||||||
|
pub a: f32,
|
||||||
|
pub b: f32,
|
||||||
|
pub c: f32,
|
||||||
|
pub d: f32,
|
||||||
|
pub e: f32,
|
||||||
|
pub f: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matrix {
|
||||||
|
pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
|
||||||
|
Self { a, b, c, d, e, f }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(x: f32, y: f32) -> Self {
|
||||||
|
Self::new(0.0, 0.0, 0.0, 0.0, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identity() -> Self {
|
||||||
|
Self {
|
||||||
|
a: 1.,
|
||||||
|
b: 0.,
|
||||||
|
c: 0.,
|
||||||
|
d: 1.,
|
||||||
|
e: 0.,
|
||||||
|
f: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_skia_matrix(&self) -> skia::Matrix {
|
||||||
|
let mut res = skia::Matrix::new_identity();
|
||||||
|
|
||||||
|
let (translate_x, translate_y) = self.translation();
|
||||||
|
let (scale_x, scale_y) = self.scale();
|
||||||
|
let (skew_x, skew_y) = self.skew();
|
||||||
|
res.set_all(
|
||||||
|
scale_x,
|
||||||
|
skew_x,
|
||||||
|
translate_x,
|
||||||
|
skew_y,
|
||||||
|
scale_y,
|
||||||
|
translate_y,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_translation(&self) -> Self {
|
||||||
|
let mut res = Self::identity();
|
||||||
|
res.c = self.c;
|
||||||
|
res.b = self.b;
|
||||||
|
res.a = self.a;
|
||||||
|
res.d = self.d;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translation(&self) -> (f32, f32) {
|
||||||
|
(self.e, self.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale(&self) -> (f32, f32) {
|
||||||
|
(self.a, self.d)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skew(&self) -> (f32, f32) {
|
||||||
|
(self.c, self.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn product(&self, other: &Matrix) -> Matrix {
|
||||||
|
let a = self.a * other.a + self.c * other.b;
|
||||||
|
let b = self.b * other.a + self.d * other.b;
|
||||||
|
let c = self.a * other.c + self.c * other.d;
|
||||||
|
let d = self.b * other.c + self.d * other.d;
|
||||||
|
let e = self.a * other.e + self.c * other.f + self.e;
|
||||||
|
let f = self.b * other.e + self.d * other.f + self.f;
|
||||||
|
Matrix::new(a, b, c, d, e, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> [u8; 24] {
|
||||||
|
let mut result = [0; 24];
|
||||||
|
result[0..4].clone_from_slice(&self.a.to_le_bytes());
|
||||||
|
result[4..8].clone_from_slice(&self.b.to_le_bytes());
|
||||||
|
result[8..12].clone_from_slice(&self.c.to_le_bytes());
|
||||||
|
result[12..16].clone_from_slice(&self.d.to_le_bytes());
|
||||||
|
result[16..20].clone_from_slice(&self.e.to_le_bytes());
|
||||||
|
result[20..24].clone_from_slice(&self.f.to_le_bytes());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_product() {
|
||||||
|
let a = Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
|
||||||
|
let b = Matrix::new(6.0, 5.0, 4.0, 3.0, 2.0, 1.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
a.product(&b),
|
||||||
|
Matrix::new(21.0, 32.0, 13.0, 20.0, 10.0, 14.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
let a = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0);
|
||||||
|
let b = Matrix::new(7.0, 4.0, 8.0, 3.0, 9.0, 5.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
a.product(&b),
|
||||||
|
Matrix::new(81.0, 40.0, 80.0, 41.0, 112.0, 56.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,20 @@ pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_bytes() {
|
pub fn write_bytes(bytes: Vec<u8>) -> *mut u8 {
|
||||||
|
if unsafe { BUFFERU8.is_some() } {
|
||||||
|
panic!("Bytes already allocated");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = Box::new(bytes);
|
||||||
|
let ptr = buffer.as_mut_ptr();
|
||||||
|
|
||||||
|
unsafe { BUFFERU8 = Some(buffer) };
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn free_bytes() {
|
||||||
if unsafe { BUFFERU8.is_some() } {
|
if unsafe { BUFFERU8.is_some() } {
|
||||||
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
||||||
std::mem::drop(buffer);
|
std::mem::drop(buffer);
|
||||||
|
@ -30,3 +43,31 @@ pub fn bytes() -> Vec<u8> {
|
||||||
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer");
|
||||||
*buffer
|
*buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait SerializableResult {
|
||||||
|
type BytesType;
|
||||||
|
fn from_bytes(bytes: Self::BytesType) -> Self;
|
||||||
|
fn as_bytes(&self) -> Self::BytesType;
|
||||||
|
fn clone_to_slice(&self, slice: &mut [u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns an array in the heap. The first 4 bytes is always the size
|
||||||
|
of the array. Then the items are serialized one after the other
|
||||||
|
by the implementation of SerializableResult trait
|
||||||
|
*/
|
||||||
|
pub fn write_vec<T: SerializableResult>(result: Vec<T>) -> *mut u8 {
|
||||||
|
let elem_size = size_of::<T>();
|
||||||
|
let bytes_len = 4 + result.len() * elem_size;
|
||||||
|
let mut result_bytes = Vec::<u8>::with_capacity(bytes_len);
|
||||||
|
|
||||||
|
result_bytes.resize(bytes_len, 0);
|
||||||
|
result_bytes[0..4].clone_from_slice(&result.len().to_le_bytes());
|
||||||
|
|
||||||
|
for i in 0..result.len() {
|
||||||
|
let base = 4 + i * elem_size;
|
||||||
|
result[i].clone_to_slice(&mut result_bytes[base..base + elem_size]);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_bytes(result_bytes)
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::math;
|
use crate::math;
|
||||||
|
use crate::matrix::Matrix;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
|
|
||||||
mod blend;
|
mod blend;
|
||||||
|
@ -232,7 +233,18 @@ impl RenderState {
|
||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(skia::Color::TRANSPARENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape(&mut self, shape: &mut Shape, clip_bounds: Option<skia::Rect>) {
|
pub fn render_shape(
|
||||||
|
&mut self,
|
||||||
|
shape: &mut Shape,
|
||||||
|
modifiers: Option<&Matrix>,
|
||||||
|
clip_bounds: Option<skia::Rect>,
|
||||||
|
) {
|
||||||
|
if let Some(modifiers) = modifiers {
|
||||||
|
self.drawing_surface
|
||||||
|
.canvas()
|
||||||
|
.concat(&modifiers.to_skia_matrix());
|
||||||
|
}
|
||||||
|
|
||||||
let transform = shape.transform.to_skia_matrix();
|
let transform = shape.transform.to_skia_matrix();
|
||||||
|
|
||||||
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
||||||
|
@ -289,6 +301,7 @@ impl RenderState {
|
||||||
pub fn start_render_loop(
|
pub fn start_render_loop(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut HashMap<Uuid, Shape>,
|
tree: &mut HashMap<Uuid, Shape>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
|
@ -304,7 +317,7 @@ impl RenderState {
|
||||||
self.translate(self.viewbox.pan_x, self.viewbox.pan_y);
|
self.translate(self.viewbox.pan_x, self.viewbox.pan_y);
|
||||||
self.pending_nodes = vec![(Uuid::nil(), false, None)];
|
self.pending_nodes = vec![(Uuid::nil(), false, None)];
|
||||||
self.render_in_progress = true;
|
self.render_in_progress = true;
|
||||||
self.process_animation_frame(tree, timestamp)?;
|
self.process_animation_frame(tree, modifiers, timestamp)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,10 +338,11 @@ impl RenderState {
|
||||||
pub fn process_animation_frame(
|
pub fn process_animation_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut HashMap<Uuid, Shape>,
|
tree: &mut HashMap<Uuid, Shape>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
self.render_shape_tree(tree, timestamp)?;
|
self.render_shape_tree(tree, modifiers, timestamp)?;
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
if let Some(frame_id) = self.render_request_id {
|
if let Some(frame_id) = self.render_request_id {
|
||||||
self.cancel_animation_frame(frame_id);
|
self.cancel_animation_frame(frame_id);
|
||||||
|
@ -395,6 +409,7 @@ impl RenderState {
|
||||||
pub fn render_shape_tree(
|
pub fn render_shape_tree(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &HashMap<Uuid, Shape>,
|
tree: &HashMap<Uuid, Shape>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !self.render_in_progress {
|
if !self.render_in_progress {
|
||||||
|
@ -432,7 +447,11 @@ impl RenderState {
|
||||||
|
|
||||||
self.drawing_surface.canvas().save();
|
self.drawing_surface.canvas().save();
|
||||||
if !node_id.is_nil() {
|
if !node_id.is_nil() {
|
||||||
self.render_shape(&mut element.clone(), clip_bounds);
|
self.render_shape(
|
||||||
|
&mut element.clone(),
|
||||||
|
modifiers.get(&element.id),
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.apply_drawing_to_render_canvas();
|
self.apply_drawing_to_render_canvas();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,25 +3,26 @@ use skia_safe as skia;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::matrix::Matrix;
|
||||||
use crate::render::BlendMode;
|
use crate::render::BlendMode;
|
||||||
|
|
||||||
mod blurs;
|
mod blurs;
|
||||||
mod bools;
|
mod bools;
|
||||||
mod fills;
|
mod fills;
|
||||||
mod matrix;
|
|
||||||
mod paths;
|
mod paths;
|
||||||
mod shadows;
|
mod shadows;
|
||||||
mod strokes;
|
mod strokes;
|
||||||
mod svgraw;
|
mod svgraw;
|
||||||
|
mod transform;
|
||||||
|
|
||||||
pub use blurs::*;
|
pub use blurs::*;
|
||||||
pub use bools::*;
|
pub use bools::*;
|
||||||
pub use fills::*;
|
pub use fills::*;
|
||||||
use matrix::*;
|
|
||||||
pub use paths::*;
|
pub use paths::*;
|
||||||
pub use shadows::*;
|
pub use shadows::*;
|
||||||
pub use strokes::*;
|
pub use strokes::*;
|
||||||
pub use svgraw::*;
|
pub use svgraw::*;
|
||||||
|
pub use transform::*;
|
||||||
|
|
||||||
pub type CornerRadius = skia::Point;
|
pub type CornerRadius = skia::Point;
|
||||||
pub type Corners = [CornerRadius; 4];
|
pub type Corners = [CornerRadius; 4];
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
use skia_safe as skia;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Matrix {
|
|
||||||
pub a: f32,
|
|
||||||
pub b: f32,
|
|
||||||
pub c: f32,
|
|
||||||
pub d: f32,
|
|
||||||
pub e: f32,
|
|
||||||
pub f: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Matrix {
|
|
||||||
pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
|
|
||||||
Self { a, b, c, d, e, f }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn identity() -> Self {
|
|
||||||
Self {
|
|
||||||
a: 1.,
|
|
||||||
b: 0.,
|
|
||||||
c: 0.,
|
|
||||||
d: 1.,
|
|
||||||
e: 0.,
|
|
||||||
f: 0.,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_skia_matrix(&self) -> skia::Matrix {
|
|
||||||
let mut res = skia::Matrix::new_identity();
|
|
||||||
|
|
||||||
let (translate_x, translate_y) = self.translation();
|
|
||||||
let (scale_x, scale_y) = self.scale();
|
|
||||||
let (skew_x, skew_y) = self.skew();
|
|
||||||
res.set_all(
|
|
||||||
scale_x,
|
|
||||||
skew_x,
|
|
||||||
translate_x,
|
|
||||||
skew_y,
|
|
||||||
scale_y,
|
|
||||||
translate_y,
|
|
||||||
0.,
|
|
||||||
0.,
|
|
||||||
1.,
|
|
||||||
);
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn no_translation(&self) -> Self {
|
|
||||||
let mut res = Self::identity();
|
|
||||||
res.c = self.c;
|
|
||||||
res.b = self.b;
|
|
||||||
res.a = self.a;
|
|
||||||
res.d = self.d;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn translation(&self) -> (f32, f32) {
|
|
||||||
(self.e, self.f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scale(&self) -> (f32, f32) {
|
|
||||||
(self.a, self.d)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skew(&self) -> (f32, f32) {
|
|
||||||
(self.c, self.b)
|
|
||||||
}
|
|
||||||
}
|
|
71
render-wasm/src/shapes/transform.rs
Normal file
71
render-wasm/src/shapes/transform.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::matrix::Matrix;
|
||||||
|
use crate::mem::SerializableResult;
|
||||||
|
use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct TransformEntry {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub transform: Matrix,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializableResult for TransformEntry {
|
||||||
|
type BytesType = [u8; size_of::<TransformEntry>()];
|
||||||
|
|
||||||
|
fn from_bytes(bytes: Self::BytesType) -> Self {
|
||||||
|
let id = uuid_from_u32_quartet(
|
||||||
|
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
|
||||||
|
u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
|
||||||
|
u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
|
||||||
|
u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let transform = Matrix::new(
|
||||||
|
f32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
|
||||||
|
f32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]),
|
||||||
|
f32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
|
||||||
|
f32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]),
|
||||||
|
f32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
|
||||||
|
f32::from_le_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]),
|
||||||
|
);
|
||||||
|
|
||||||
|
TransformEntry { id, transform }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::BytesType {
|
||||||
|
let mut result: [u8; 40] = [0; 40];
|
||||||
|
let (a, b, c, d) = uuid_to_u32_quartet(&self.id);
|
||||||
|
result[0..4].clone_from_slice(&a.to_le_bytes());
|
||||||
|
result[4..8].clone_from_slice(&b.to_le_bytes());
|
||||||
|
result[8..12].clone_from_slice(&c.to_le_bytes());
|
||||||
|
result[12..16].clone_from_slice(&d.to_le_bytes());
|
||||||
|
result[16..40].clone_from_slice(&self.transform.as_bytes());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic trait doesn't know the size of the array. This is why the
|
||||||
|
// clone needs to be here even if it could be generic.
|
||||||
|
fn clone_to_slice(&self, slice: &mut [u8]) {
|
||||||
|
slice.clone_from_slice(&self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let entry = TransformEntry {
|
||||||
|
id: uuid!("550e8400-e29b-41d4-a716-446655440000"),
|
||||||
|
transform: Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = entry.as_bytes();
|
||||||
|
|
||||||
|
assert_eq!(entry, TransformEntry::from_bytes(bytes));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::matrix;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ pub(crate) struct State<'a> {
|
||||||
pub current_id: Option<Uuid>,
|
pub current_id: Option<Uuid>,
|
||||||
pub current_shape: Option<&'a mut Shape>,
|
pub current_shape: Option<&'a mut Shape>,
|
||||||
pub shapes: HashMap<Uuid, Shape>,
|
pub shapes: HashMap<Uuid, Shape>,
|
||||||
|
pub modifiers: HashMap<Uuid, matrix::Matrix>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
|
@ -25,6 +27,7 @@ impl<'a> State<'a> {
|
||||||
current_id: None,
|
current_id: None,
|
||||||
current_shape: None,
|
current_shape: None,
|
||||||
shapes: HashMap::with_capacity(capacity),
|
shapes: HashMap::with_capacity(capacity),
|
||||||
|
modifiers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +41,13 @@ impl<'a> State<'a> {
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(&mut self.shapes, timestamp)?;
|
.start_render_loop(&mut self.shapes, &self.modifiers, timestamp)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.process_animation_frame(&mut self.shapes, timestamp)?;
|
.process_animation_frame(&mut self.shapes, &self.modifiers, timestamp)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,12 @@ pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid {
|
||||||
let lo: u64 = ((c as u64) << 32) | d as u64;
|
let lo: u64 = ((c as u64) << 32) | d as u64;
|
||||||
Uuid::from_u64_pair(hi, lo)
|
Uuid::from_u64_pair(hi, lo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uuid_to_u32_quartet(id: &Uuid) -> (u32, u32, u32, u32) {
|
||||||
|
let (hi, lo) = id.as_u64_pair();
|
||||||
|
let hihi32 = (hi >> 32) as u32;
|
||||||
|
let hilo32 = hi as u32;
|
||||||
|
let lohi32 = (lo >> 32) as u32;
|
||||||
|
let lolo32 = lo as u32;
|
||||||
|
(hihi32, hilo32, lohi32, lolo32)
|
||||||
|
}
|
||||||
|
|
8
render-wasm/watch_test
Executable file
8
render-wasm/watch_test
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
_SCRIPT_DIR=$(dirname $0);
|
||||||
|
|
||||||
|
export SKIA_BINARIES_URL="https://github.com/rust-skia/skia-binaries/releases/download/0.80.0/skia-binaries-9e7d2684a17084095aef-x86_64-unknown-linux-gnu-egl-gl-svg-textlayout-vulkan-wayland-webpd-webpe-x11.tar.gz"
|
||||||
|
|
||||||
|
pushd $_SCRIPT_DIR;
|
||||||
|
cargo watch -x "test --bin render_wasm -- --show-output"
|
||||||
|
popd
|
Loading…
Add table
Add a link
Reference in a new issue