mirror of
https://github.com/penpot/penpot.git
synced 2025-05-16 10:26:41 +02:00
Merge pull request #5201 from penpot/niwinz-shape-with-selrect-as-f32-array
✨ Shape with buffer
This commit is contained in:
commit
fa4f2aa5cc
13 changed files with 518 additions and 228 deletions
|
@ -499,7 +499,7 @@
|
||||||
object
|
object
|
||||||
(-> object
|
(-> object
|
||||||
(update :selrect grc/make-rect)
|
(update :selrect grc/make-rect)
|
||||||
(cts/map->Shape))))
|
(cts/create-shape))))
|
||||||
(update-container [container]
|
(update-container [container]
|
||||||
(d/update-when container :objects update-vals update-object))]
|
(d/update-when container :objects update-vals update-object))]
|
||||||
(-> data
|
(-> data
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
:width (mth/abs (- x2 x1))
|
:width (mth/abs (- x2 x1))
|
||||||
:height (mth/abs (- y2 y1))))
|
:height (mth/abs (- y2 y1))))
|
||||||
|
|
||||||
|
;; FIXME: looks unused
|
||||||
:position
|
:position
|
||||||
(let [x (dm/get-prop rect :x)
|
(let [x (dm/get-prop rect :x)
|
||||||
y (dm/get-prop rect :y)
|
y (dm/get-prop rect :y)
|
||||||
|
@ -158,22 +159,22 @@
|
||||||
y (dm/get-prop rect :y)
|
y (dm/get-prop rect :y)
|
||||||
w (dm/get-prop rect :width)
|
w (dm/get-prop rect :width)
|
||||||
h (dm/get-prop rect :height)]
|
h (dm/get-prop rect :height)]
|
||||||
(rc/assoc! rect
|
(assoc rect
|
||||||
:x1 x
|
:x1 x
|
||||||
:y1 y
|
:y1 y
|
||||||
:x2 (+ x w)
|
:x2 (+ x w)
|
||||||
:y2 (+ y h)))
|
:y2 (+ y h)))
|
||||||
|
|
||||||
:corners
|
:corners
|
||||||
(let [x1 (dm/get-prop rect :x1)
|
(let [x1 (dm/get-prop rect :x1)
|
||||||
y1 (dm/get-prop rect :y1)
|
y1 (dm/get-prop rect :y1)
|
||||||
x2 (dm/get-prop rect :x2)
|
x2 (dm/get-prop rect :x2)
|
||||||
y2 (dm/get-prop rect :y2)]
|
y2 (dm/get-prop rect :y2)]
|
||||||
(rc/assoc! rect
|
(assoc rect
|
||||||
:x (mth/min x1 x2)
|
:x (mth/min x1 x2)
|
||||||
:y (mth/min y1 y2)
|
:y (mth/min y1 y2)
|
||||||
:width (mth/abs (- x2 x1))
|
:width (mth/abs (- x2 x1))
|
||||||
:height (mth/abs (- y2 y1))))))
|
:height (mth/abs (- y2 y1))))))
|
||||||
|
|
||||||
(defn close-rect?
|
(defn close-rect?
|
||||||
[rect1 rect2]
|
[rect1 rect2]
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
[app.common.geom.shapes.common :as gco]
|
[app.common.geom.shapes.common :as gco]
|
||||||
[app.common.geom.shapes.path :as gpa]
|
[app.common.geom.shapes.path :as gpa]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.record :as cr]
|
|
||||||
[app.common.types.modifiers :as ctm]))
|
[app.common.types.modifiers :as ctm]))
|
||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
@ -280,7 +279,7 @@
|
||||||
transform (calculate-transform points center selrect)]
|
transform (calculate-transform points center selrect)]
|
||||||
[selrect transform (when (some? transform) (gmt/inverse transform))]))
|
[selrect transform (when (some? transform) (gmt/inverse transform))]))
|
||||||
|
|
||||||
(defn- adjust-shape-flips!
|
(defn- adjust-shape-flips
|
||||||
"After some tranformations the flip-x/flip-y flags can change we need
|
"After some tranformations the flip-x/flip-y flags can change we need
|
||||||
to check this before adjusting the selrect"
|
to check this before adjusting the selrect"
|
||||||
[shape points]
|
[shape points]
|
||||||
|
@ -299,16 +298,16 @@
|
||||||
|
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
(neg? dot-x)
|
(neg? dot-x)
|
||||||
(cr/update! :flip-x not)
|
(update :flip-x not)
|
||||||
|
|
||||||
(neg? dot-x)
|
(neg? dot-x)
|
||||||
(cr/update! :rotation -)
|
(update :rotation -)
|
||||||
|
|
||||||
(neg? dot-y)
|
(neg? dot-y)
|
||||||
(cr/update! :flip-y not)
|
(update :flip-y not)
|
||||||
|
|
||||||
(neg? dot-y)
|
(neg? dot-y)
|
||||||
(cr/update! :rotation -))))
|
(update :rotation -))))
|
||||||
|
|
||||||
(defn- apply-transform-move
|
(defn- apply-transform-move
|
||||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||||
|
@ -318,9 +317,6 @@
|
||||||
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
|
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
|
||||||
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
|
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
|
||||||
|
|
||||||
;; NOTE: ensure we start with a fresh copy of shape for mutabilty
|
|
||||||
shape (cr/clone shape)
|
|
||||||
|
|
||||||
shape (if (= type :bool)
|
shape (if (= type :bool)
|
||||||
(update shape :bool-content gpa/transform-content transform-mtx)
|
(update shape :bool-content gpa/transform-content transform-mtx)
|
||||||
shape)
|
shape)
|
||||||
|
@ -329,14 +325,14 @@
|
||||||
shape)
|
shape)
|
||||||
shape (if (= type :path)
|
shape (if (= type :path)
|
||||||
(update shape :content gpa/transform-content transform-mtx)
|
(update shape :content gpa/transform-content transform-mtx)
|
||||||
(cr/assoc! shape
|
(assoc shape
|
||||||
:x (dm/get-prop selrect :x)
|
:x (dm/get-prop selrect :x)
|
||||||
:y (dm/get-prop selrect :y)
|
:y (dm/get-prop selrect :y)
|
||||||
:width (dm/get-prop selrect :width)
|
:width (dm/get-prop selrect :width)
|
||||||
:height (dm/get-prop selrect :height)))]
|
:height (dm/get-prop selrect :height)))]
|
||||||
(-> shape
|
(-> shape
|
||||||
(cr/assoc! :selrect selrect)
|
(assoc :selrect selrect)
|
||||||
(cr/assoc! :points points))))
|
(assoc :points points))))
|
||||||
|
|
||||||
|
|
||||||
(defn- apply-transform-generic
|
(defn- apply-transform-generic
|
||||||
|
@ -346,9 +342,7 @@
|
||||||
(let [points (-> (dm/get-prop shape :points)
|
(let [points (-> (dm/get-prop shape :points)
|
||||||
(gco/transform-points transform-mtx))
|
(gco/transform-points transform-mtx))
|
||||||
|
|
||||||
;; NOTE: ensure we have a fresh shallow copy of shape
|
shape (adjust-shape-flips shape points)
|
||||||
shape (cr/clone shape)
|
|
||||||
shape (adjust-shape-flips! shape points)
|
|
||||||
|
|
||||||
center (gco/points->center points)
|
center (gco/points->center points)
|
||||||
selrect (calculate-selrect points center)
|
selrect (calculate-selrect points center)
|
||||||
|
@ -367,17 +361,17 @@
|
||||||
|
|
||||||
shape (if (= type :path)
|
shape (if (= type :path)
|
||||||
(update shape :content gpa/transform-content transform-mtx)
|
(update shape :content gpa/transform-content transform-mtx)
|
||||||
(cr/assoc! shape
|
(assoc shape
|
||||||
:x (dm/get-prop selrect :x)
|
:x (dm/get-prop selrect :x)
|
||||||
:y (dm/get-prop selrect :y)
|
:y (dm/get-prop selrect :y)
|
||||||
:width (dm/get-prop selrect :width)
|
:width (dm/get-prop selrect :width)
|
||||||
:height (dm/get-prop selrect :height)))]
|
:height (dm/get-prop selrect :height)))]
|
||||||
(-> shape
|
(-> shape
|
||||||
(cr/assoc! :transform transform)
|
(assoc :transform transform)
|
||||||
(cr/assoc! :transform-inverse inverse)
|
(assoc :transform-inverse inverse)
|
||||||
(cr/assoc! :selrect selrect)
|
(assoc :selrect selrect)
|
||||||
(cr/assoc! :points points)
|
(assoc :points points)
|
||||||
(cr/assoc! :rotation rotation))))))
|
(assoc :rotation rotation))))))
|
||||||
|
|
||||||
(defn- apply-transform
|
(defn- apply-transform
|
||||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
(ns app.common.types.shape
|
(ns app.common.types.shape
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [app.common.fressian :as fres])
|
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
|
@ -14,16 +13,15 @@
|
||||||
[app.common.geom.proportions :as gpr]
|
[app.common.geom.proportions :as gpr]
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.record :as cr]
|
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.schema.generators :as sg]
|
[app.common.schema.generators :as sg]
|
||||||
[app.common.transit :as t]
|
|
||||||
[app.common.types.color :as ctc]
|
[app.common.types.color :as ctc]
|
||||||
[app.common.types.grid :as ctg]
|
[app.common.types.grid :as ctg]
|
||||||
[app.common.types.plugins :as ctpg]
|
[app.common.types.plugins :as ctpg]
|
||||||
[app.common.types.shape.attrs :refer [default-color]]
|
[app.common.types.shape.attrs :refer [default-color]]
|
||||||
[app.common.types.shape.blur :as ctsb]
|
[app.common.types.shape.blur :as ctsb]
|
||||||
[app.common.types.shape.export :as ctse]
|
[app.common.types.shape.export :as ctse]
|
||||||
|
[app.common.types.shape.impl :as impl]
|
||||||
[app.common.types.shape.interactions :as ctsi]
|
[app.common.types.shape.interactions :as ctsi]
|
||||||
[app.common.types.shape.layout :as ctsl]
|
[app.common.types.shape.layout :as ctsl]
|
||||||
[app.common.types.shape.path :as ctsp]
|
[app.common.types.shape.path :as ctsp]
|
||||||
|
@ -32,11 +30,9 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(cr/defrecord Shape [id name type x y width height rotation selrect points transform transform-inverse parent-id frame-id flip-x flip-y])
|
|
||||||
|
|
||||||
(defn shape?
|
(defn shape?
|
||||||
[o]
|
[o]
|
||||||
(instance? Shape o))
|
(impl/shape? o))
|
||||||
|
|
||||||
(def stroke-caps-line #{:round :square})
|
(def stroke-caps-line #{:round :square})
|
||||||
(def stroke-caps-marker #{:line-arrow :triangle-arrow :square-marker :circle-marker :diamond-marker})
|
(def stroke-caps-marker #{:line-arrow :triangle-arrow :square-marker :circle-marker :diamond-marker})
|
||||||
|
@ -244,7 +240,7 @@
|
||||||
(defn- decode-shape
|
(defn- decode-shape
|
||||||
[o]
|
[o]
|
||||||
(if (map? o)
|
(if (map? o)
|
||||||
(map->Shape o)
|
(impl/map->Shape o)
|
||||||
o))
|
o))
|
||||||
|
|
||||||
(defn- shape-generator
|
(defn- shape-generator
|
||||||
|
@ -268,7 +264,7 @@
|
||||||
(= type :bool))
|
(= type :bool))
|
||||||
(merge attrs1 shape attrs3)
|
(merge attrs1 shape attrs3)
|
||||||
(merge attrs1 shape attrs2 attrs3)))))
|
(merge attrs1 shape attrs2 attrs3)))))
|
||||||
(sg/fmap map->Shape)))
|
(sg/fmap impl/map->Shape)))
|
||||||
|
|
||||||
(def schema:shape
|
(def schema:shape
|
||||||
[:and {:title "Shape"
|
[:and {:title "Shape"
|
||||||
|
@ -455,27 +451,30 @@
|
||||||
;; NOTE: used for create ephimeral shapes for multiple selection
|
;; NOTE: used for create ephimeral shapes for multiple selection
|
||||||
:multiple minimal-multiple-attrs))
|
:multiple minimal-multiple-attrs))
|
||||||
|
|
||||||
|
(defn create-shape
|
||||||
|
"A low level function that creates a Shape data structure
|
||||||
|
from a attrs map without performing other transformations"
|
||||||
|
[attrs]
|
||||||
|
(impl/create-shape attrs))
|
||||||
|
|
||||||
(defn- make-minimal-shape
|
(defn- make-minimal-shape
|
||||||
[type]
|
[type]
|
||||||
(let [type (if (= type :curve) :path type)
|
(let [type (if (= type :curve) :path type)
|
||||||
attrs (get-minimal-shape type)]
|
attrs (get-minimal-shape type)
|
||||||
|
attrs (cond-> attrs
|
||||||
|
(and (not= :path type)
|
||||||
|
(not= :bool type))
|
||||||
|
(-> (assoc :x 0)
|
||||||
|
(assoc :y 0)
|
||||||
|
(assoc :width 0.01)
|
||||||
|
(assoc :height 0.01)))
|
||||||
|
attrs (-> attrs
|
||||||
|
(assoc :id (uuid/next))
|
||||||
|
(assoc :frame-id uuid/zero)
|
||||||
|
(assoc :parent-id uuid/zero)
|
||||||
|
(assoc :rotation 0))]
|
||||||
|
|
||||||
(cond-> attrs
|
(impl/create-shape attrs)))
|
||||||
(and (not= :path type)
|
|
||||||
(not= :bool type))
|
|
||||||
(-> (assoc :x 0)
|
|
||||||
(assoc :y 0)
|
|
||||||
(assoc :width 0.01)
|
|
||||||
(assoc :height 0.01))
|
|
||||||
|
|
||||||
:always
|
|
||||||
(assoc :id (uuid/next)
|
|
||||||
:frame-id uuid/zero
|
|
||||||
:parent-id uuid/zero
|
|
||||||
:rotation 0)
|
|
||||||
|
|
||||||
:always
|
|
||||||
(map->Shape))))
|
|
||||||
|
|
||||||
(defn setup-rect
|
(defn setup-rect
|
||||||
"Initializes the selrect and points for a shape."
|
"Initializes the selrect and points for a shape."
|
||||||
|
@ -530,17 +529,3 @@
|
||||||
(assoc :transform-inverse (gmt/matrix)))
|
(assoc :transform-inverse (gmt/matrix)))
|
||||||
(gpr/setup-proportions))))
|
(gpr/setup-proportions))))
|
||||||
|
|
||||||
;; --- SHAPE SERIALIZATION
|
|
||||||
|
|
||||||
(t/add-handlers!
|
|
||||||
{:id "shape"
|
|
||||||
:class Shape
|
|
||||||
:wfn #(into {} %)
|
|
||||||
:rfn map->Shape})
|
|
||||||
|
|
||||||
#?(:clj
|
|
||||||
(fres/add-handlers!
|
|
||||||
{:name "penpot/shape"
|
|
||||||
:class Shape
|
|
||||||
:wfn fres/write-map-like
|
|
||||||
:rfn (comp map->Shape fres/read-map-like)}))
|
|
||||||
|
|
227
common/src/app/common/types/shape/impl.cljc
Normal file
227
common/src/app/common/types/shape/impl.cljc
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.common.types.shape.impl
|
||||||
|
(:require
|
||||||
|
#?(:clj [app.common.fressian :as fres])
|
||||||
|
#?(:cljs [app.common.data.macros :as dm])
|
||||||
|
#?(:cljs [app.common.geom.rect :as grc])
|
||||||
|
#?(:cljs [cuerdas.core :as str])
|
||||||
|
[app.common.record :as cr]
|
||||||
|
[app.common.transit :as t]
|
||||||
|
[clojure.core :as c]))
|
||||||
|
|
||||||
|
(def enabled-wasm-ready-shape false)
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(do
|
||||||
|
(def ArrayBuffer js/ArrayBuffer)
|
||||||
|
(def Float32Array js/Float32Array)))
|
||||||
|
|
||||||
|
(cr/defrecord Shape [id name type x y width height rotation selrect points
|
||||||
|
transform transform-inverse parent-id frame-id flip-x flip-y])
|
||||||
|
|
||||||
|
(declare ^:private clone-f32-array)
|
||||||
|
(declare ^:private impl-assoc)
|
||||||
|
(declare ^:private impl-conj)
|
||||||
|
(declare ^:private impl-dissoc)
|
||||||
|
(declare ^:private read-selrect)
|
||||||
|
(declare ^:private write-selrect)
|
||||||
|
|
||||||
|
;; TODO: implement lazy MapEntry
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftype ShapeWithBuffer [buffer delegate]
|
||||||
|
Object
|
||||||
|
(toString [coll]
|
||||||
|
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
|
||||||
|
|
||||||
|
(equiv [this other]
|
||||||
|
(-equiv this other))
|
||||||
|
|
||||||
|
;; ICloneable
|
||||||
|
;; (-clone [_]
|
||||||
|
;; (let [bf32 (clone-float32-array buffer)]
|
||||||
|
;; (ShapeWithBuffer. bf32 delegate)))
|
||||||
|
|
||||||
|
IWithMeta
|
||||||
|
(-with-meta [_ meta]
|
||||||
|
(ShapeWithBuffer. buffer (with-meta delegate meta)))
|
||||||
|
|
||||||
|
IMeta
|
||||||
|
(-meta [_] (meta delegate))
|
||||||
|
|
||||||
|
ICollection
|
||||||
|
(-conj [coll entry]
|
||||||
|
(impl-conj coll entry))
|
||||||
|
|
||||||
|
IEquiv
|
||||||
|
(-equiv [coll other]
|
||||||
|
(c/equiv-map coll other))
|
||||||
|
|
||||||
|
IHash
|
||||||
|
(-hash [coll] (hash (into {} coll)))
|
||||||
|
|
||||||
|
ISequential
|
||||||
|
|
||||||
|
ISeqable
|
||||||
|
(-seq [coll]
|
||||||
|
(cons (find coll :selrect)
|
||||||
|
(seq delegate)))
|
||||||
|
|
||||||
|
ICounted
|
||||||
|
(-count [_]
|
||||||
|
(+ 1 (count delegate)))
|
||||||
|
|
||||||
|
ILookup
|
||||||
|
(-lookup [coll k]
|
||||||
|
(-lookup coll k nil))
|
||||||
|
|
||||||
|
(-lookup [_ k not-found]
|
||||||
|
(if (= k :selrect)
|
||||||
|
(read-selrect buffer)
|
||||||
|
(c/-lookup delegate k not-found)))
|
||||||
|
|
||||||
|
IFind
|
||||||
|
(-find [_ k]
|
||||||
|
(if (= k :selrect)
|
||||||
|
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
|
||||||
|
(c/-find delegate k)))
|
||||||
|
|
||||||
|
IAssociative
|
||||||
|
(-assoc [coll k v]
|
||||||
|
(impl-assoc coll k v))
|
||||||
|
|
||||||
|
(-contains-key? [_ k]
|
||||||
|
(or (= k :selrect)
|
||||||
|
(contains? delegate k)))
|
||||||
|
|
||||||
|
IMap
|
||||||
|
(-dissoc [coll k]
|
||||||
|
(impl-dissoc coll k))
|
||||||
|
|
||||||
|
IFn
|
||||||
|
(-invoke [coll k]
|
||||||
|
(-lookup coll k))
|
||||||
|
|
||||||
|
(-invoke [coll k not-found]
|
||||||
|
(-lookup coll k not-found))
|
||||||
|
|
||||||
|
IPrintWithWriter
|
||||||
|
(-pr-writer [_ writer _]
|
||||||
|
(-write writer (str "#penpot/shape " (:id delegate))))))
|
||||||
|
|
||||||
|
(defn shape?
|
||||||
|
[o]
|
||||||
|
#?(:clj (instance? Shape o)
|
||||||
|
:cljs (or (instance? Shape o)
|
||||||
|
(instance? ShapeWithBuffer o))))
|
||||||
|
|
||||||
|
;; --- SHAPE IMPL
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- clone-f32-array
|
||||||
|
[^Float32Array src]
|
||||||
|
(let [copy (new Float32Array (.-length src))]
|
||||||
|
(.set copy src)
|
||||||
|
copy)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- write-selrect
|
||||||
|
"Write the selrect into the buffer"
|
||||||
|
[data selrect]
|
||||||
|
(assert (instance? Float32Array data) "expected instance of float32array")
|
||||||
|
|
||||||
|
(aset data 0 (dm/get-prop selrect :x1))
|
||||||
|
(aset data 1 (dm/get-prop selrect :y1))
|
||||||
|
(aset data 2 (dm/get-prop selrect :x2))
|
||||||
|
(aset data 3 (dm/get-prop selrect :y2))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- read-selrect
|
||||||
|
"Read selrect from internal buffer"
|
||||||
|
[^Float32Array buffer]
|
||||||
|
(let [x1 (aget buffer 0)
|
||||||
|
y1 (aget buffer 1)
|
||||||
|
x2 (aget buffer 2)
|
||||||
|
y2 (aget buffer 3)]
|
||||||
|
(grc/make-rect x1 y1
|
||||||
|
(- x2 x1)
|
||||||
|
(- y2 y1)))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- impl-assoc
|
||||||
|
[coll k v]
|
||||||
|
(if (= k :selrect)
|
||||||
|
(let [buffer (clone-f32-array (.-buffer coll))]
|
||||||
|
(write-selrect buffer v)
|
||||||
|
(ShapeWithBuffer. buffer (.-delegate coll)))
|
||||||
|
|
||||||
|
(let [delegate (.-delegate coll)
|
||||||
|
delegate' (assoc delegate k v)]
|
||||||
|
(if (identical? delegate' delegate)
|
||||||
|
coll
|
||||||
|
(let [buffer (clone-f32-array (.-buffer coll))]
|
||||||
|
(ShapeWithBuffer. buffer delegate')))))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- impl-dissoc
|
||||||
|
[coll k]
|
||||||
|
(let [delegate (.-delegate coll)
|
||||||
|
delegate' (dissoc delegate k)]
|
||||||
|
(if (identical? delegate delegate')
|
||||||
|
coll
|
||||||
|
(let [buffer (clone-f32-array (.-buffer coll))]
|
||||||
|
(ShapeWithBuffer. buffer delegate'))))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn- impl-conj
|
||||||
|
[coll entry]
|
||||||
|
(if (vector? entry)
|
||||||
|
(-assoc coll (-nth entry 0) (-nth entry 1))
|
||||||
|
(loop [ret coll es (seq entry)]
|
||||||
|
(if (nil? es)
|
||||||
|
ret
|
||||||
|
(let [e (first es)]
|
||||||
|
(if (vector? e)
|
||||||
|
(recur (-assoc ret (-nth e 0) (-nth e 1))
|
||||||
|
(next es))
|
||||||
|
(throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))))
|
||||||
|
|
||||||
|
(defn create-shape
|
||||||
|
"Instanciate a shape from a map"
|
||||||
|
[attrs]
|
||||||
|
#?(:cljs
|
||||||
|
(if enabled-wasm-ready-shape
|
||||||
|
(let [selrect (:selrect attrs)
|
||||||
|
buffer (new Float32Array 4)]
|
||||||
|
(write-selrect buffer selrect)
|
||||||
|
(ShapeWithBuffer. buffer (dissoc attrs :selrect)))
|
||||||
|
(map->Shape attrs))
|
||||||
|
|
||||||
|
:clj (map->Shape attrs)))
|
||||||
|
|
||||||
|
;; --- SHAPE SERIALIZATION
|
||||||
|
|
||||||
|
(t/add-handlers!
|
||||||
|
{:id "shape"
|
||||||
|
:class Shape
|
||||||
|
:wfn #(into {} %)
|
||||||
|
:rfn create-shape})
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(t/add-handlers!
|
||||||
|
{:id "shape"
|
||||||
|
:class ShapeWithBuffer
|
||||||
|
:wfn #(into {} %)
|
||||||
|
:rfn create-shape}))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(fres/add-handlers!
|
||||||
|
{:name "penpot/shape"
|
||||||
|
:class Shape
|
||||||
|
:wfn fres/write-map-like
|
||||||
|
:rfn (comp create-shape fres/read-map-like)}))
|
|
@ -15,7 +15,6 @@
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.logic.libraries :as cll]
|
[app.common.logic.libraries :as cll]
|
||||||
[app.common.record :as cr]
|
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.changes :as dch]
|
[app.main.data.changes :as dch]
|
||||||
|
@ -68,15 +67,15 @@
|
||||||
|
|
||||||
calculate-selrect
|
calculate-selrect
|
||||||
(fn [selrect [delta space?]]
|
(fn [selrect [delta space?]]
|
||||||
(let [selrect (-> (cr/clone selrect)
|
(let [selrect (-> selrect
|
||||||
(cr/update! :x2 + (:x delta))
|
(update :x2 + (:x delta))
|
||||||
(cr/update! :y2 + (:y delta)))
|
(update :y2 + (:y delta)))
|
||||||
selrect (if ^boolean space?
|
selrect (if ^boolean space?
|
||||||
(-> selrect
|
(-> selrect
|
||||||
(cr/update! :x1 + (:x delta))
|
(update :x1 + (:x delta))
|
||||||
(cr/update! :y1 + (:y delta)))
|
(update :y1 + (:y delta)))
|
||||||
selrect)]
|
selrect)]
|
||||||
(grc/update-rect! selrect :corners)))
|
(grc/update-rect selrect :corners)))
|
||||||
|
|
||||||
selrect-stream
|
selrect-stream
|
||||||
(->> ms/mouse-position
|
(->> ms/mouse-position
|
||||||
|
|
|
@ -6,32 +6,20 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.shapes.common
|
(ns app.main.ui.workspace.shapes.common
|
||||||
(:require
|
(:require
|
||||||
[app.common.record :as cr]
|
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.workspace.shapes.debug :as wsd]
|
[app.main.ui.workspace.shapes.debug :as wsd]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(def ^:private excluded-attrs
|
|
||||||
#{:blocked
|
|
||||||
:hide-fill-on-export
|
|
||||||
:collapsed
|
|
||||||
:remote-synced
|
|
||||||
:exports})
|
|
||||||
|
|
||||||
(defn check-shape
|
|
||||||
[new-shape old-shape]
|
|
||||||
(cr/-equiv-with-exceptions old-shape new-shape excluded-attrs))
|
|
||||||
|
|
||||||
(defn check-shape-props
|
(defn check-shape-props
|
||||||
[np op]
|
[np op]
|
||||||
(check-shape (unchecked-get np "shape")
|
(= (unchecked-get np "shape")
|
||||||
(unchecked-get op "shape")))
|
(unchecked-get op "shape")))
|
||||||
|
|
||||||
(defn generic-wrapper-factory
|
(defn generic-wrapper-factory
|
||||||
[component]
|
[component]
|
||||||
(mf/fnc generic-wrapper
|
(mf/fnc generic-wrapper
|
||||||
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
{::mf/wrap [#(mf/memo' % check-shape-props)]
|
||||||
::mf/wrap-props false}
|
::mf/props :obj}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")]
|
(let [shape (unchecked-get props "shape")]
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
|
|
|
@ -134,13 +134,13 @@
|
||||||
hover-top-frame-id (mf/use-state nil)
|
hover-top-frame-id (mf/use-state nil)
|
||||||
frame-hover (mf/use-state nil)
|
frame-hover (mf/use-state nil)
|
||||||
active-frames (mf/use-state #{})
|
active-frames (mf/use-state #{})
|
||||||
|
canvas-init? (mf/use-state false)
|
||||||
|
|
||||||
;; REFS
|
;; REFS
|
||||||
[viewport-ref
|
[viewport-ref
|
||||||
on-viewport-ref] (create-viewport-ref)
|
on-viewport-ref] (create-viewport-ref)
|
||||||
|
|
||||||
canvas-ref (mf/use-ref nil)
|
canvas-ref (mf/use-ref nil)
|
||||||
canvas-init (mf/use-ref false)
|
|
||||||
|
|
||||||
;; VARS
|
;; VARS
|
||||||
disable-paste (mf/use-var false)
|
disable-paste (mf/use-var false)
|
||||||
|
@ -261,7 +261,8 @@
|
||||||
(= (:layout selected-frame) :flex)
|
(= (:layout selected-frame) :flex)
|
||||||
(zero? (:rotation first-shape)))
|
(zero? (:rotation first-shape)))
|
||||||
|
|
||||||
selecting-first-level-frame? (and single-select? (cfh/root-frame? first-shape))
|
selecting-first-level-frame?
|
||||||
|
(and single-select? (cfh/root-frame? first-shape))
|
||||||
|
|
||||||
offset-x (if selecting-first-level-frame?
|
offset-x (if selecting-first-level-frame?
|
||||||
(:x first-shape)
|
(:x first-shape)
|
||||||
|
@ -280,14 +281,20 @@
|
||||||
(->> render.wasm/module
|
(->> render.wasm/module
|
||||||
(p/fmap (fn [ready?]
|
(p/fmap (fn [ready?]
|
||||||
(when ready?
|
(when ready?
|
||||||
(mf/set-ref-val! canvas-init true)
|
(reset! canvas-init? true)
|
||||||
(render.wasm/assign-canvas canvas)))))
|
(render.wasm/assign-canvas canvas)))))
|
||||||
(fn []
|
(fn []
|
||||||
(render.wasm/clear-canvas))))
|
(render.wasm/clear-canvas))))
|
||||||
|
|
||||||
(mf/with-effect [vbox' base-objects]
|
(mf/with-effect [objects-modified canvas-init?]
|
||||||
(when (mf/ref-val canvas-init)
|
(when @canvas-init?
|
||||||
(render.wasm/draw-objects base-objects zoom vbox'))))
|
(render.wasm/set-objects objects-modified)
|
||||||
|
(render.wasm/draw-objects zoom vbox)))
|
||||||
|
|
||||||
|
(mf/with-effect [vbox canvas-init?]
|
||||||
|
(let [frame-id (when @canvas-init? (do
|
||||||
|
(render.wasm/draw-objects zoom vbox)))]
|
||||||
|
(partial render.wasm/cancel-draw frame-id))))
|
||||||
|
|
||||||
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
|
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
|
||||||
(hooks/setup-viewport-size vport viewport-ref)
|
(hooks/setup-viewport-size vport viewport-ref)
|
||||||
|
@ -295,6 +302,8 @@
|
||||||
(hooks/setup-keyboard alt? mod? space? z? shift?)
|
(hooks/setup-keyboard alt? mod? space? z? shift?)
|
||||||
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
|
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
|
||||||
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
|
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
|
||||||
|
|
||||||
|
;; FIXME: this should be removed on canvas viewport
|
||||||
(hooks/setup-viewport-modifiers modifiers base-objects)
|
(hooks/setup-viewport-modifiers modifiers base-objects)
|
||||||
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
|
(hooks/setup-shortcuts node-editing? drawing-path? text-editing? grid-editing?)
|
||||||
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
|
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
|
||||||
|
|
|
@ -8,44 +8,75 @@
|
||||||
"A WASM based render API"
|
"A WASM based render API"
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.files.helpers :as cfh]
|
||||||
|
[app.common.types.shape.impl]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
(def enabled?
|
(def enabled?
|
||||||
(contains? cf/flags :render-wasm))
|
(contains? cf/flags :render-wasm))
|
||||||
|
|
||||||
(defonce ^:dynamic internal-module #js {})
|
(set! app.common.types.shape.impl/enabled-wasm-ready-shape enabled?)
|
||||||
(defonce ^:dynamic internal-gpu-state #js {})
|
|
||||||
|
|
||||||
(defn draw-objects [objects zoom vbox]
|
(defonce internal-module #js {})
|
||||||
(let [draw-rect (unchecked-get internal-module "_draw_rect")
|
(defonce internal-gpu-state #js {})
|
||||||
translate (unchecked-get internal-module "_translate")
|
|
||||||
reset-canvas (unchecked-get internal-module "_reset_canvas")
|
|
||||||
scale (unchecked-get internal-module "_scale")
|
|
||||||
flush (unchecked-get internal-module "_flush")
|
|
||||||
gpu-state internal-gpu-state]
|
|
||||||
|
|
||||||
|
;; TODO: remove the `take` once we have the dynamic data structure in Rust
|
||||||
|
(def xform
|
||||||
|
(comp
|
||||||
|
(remove cfh/root?)
|
||||||
|
(take 2048)))
|
||||||
|
|
||||||
|
;; Size in number of f32 values that represents the shape selrect (
|
||||||
|
(def rect-size 4)
|
||||||
|
|
||||||
|
(defn set-objects
|
||||||
|
[objects]
|
||||||
|
;; FIXME: maybe change the name of `_shapes_buffer` (?)
|
||||||
|
(let [get-shapes-buffer-ptr
|
||||||
|
(unchecked-get internal-module "_shapes_buffer")
|
||||||
|
|
||||||
|
heap
|
||||||
|
(unchecked-get internal-module "HEAPF32")
|
||||||
|
|
||||||
|
shapes
|
||||||
|
(into [] xform (vals objects))
|
||||||
|
|
||||||
|
total-shapes
|
||||||
|
(count shapes)
|
||||||
|
|
||||||
|
heap-offset
|
||||||
|
(get-shapes-buffer-ptr)
|
||||||
|
|
||||||
|
heap-size
|
||||||
|
(* rect-size total-shapes)
|
||||||
|
|
||||||
|
mem
|
||||||
|
(js/Float32Array. (.-buffer heap)
|
||||||
|
heap-offset
|
||||||
|
heap-size)]
|
||||||
|
|
||||||
|
(loop [index 0]
|
||||||
|
(when (< index total-shapes)
|
||||||
|
(let [shape (nth shapes index)]
|
||||||
|
(.set ^js mem (.-buffer shape) (* index rect-size))
|
||||||
|
(recur (inc index)))))))
|
||||||
|
|
||||||
|
(defn draw-objects
|
||||||
|
[zoom vbox]
|
||||||
|
(let [draw-all-shapes (unchecked-get internal-module "_draw_all_shapes")]
|
||||||
(js/requestAnimationFrame
|
(js/requestAnimationFrame
|
||||||
(fn []
|
(fn []
|
||||||
(reset-canvas gpu-state)
|
(let [pan-x (- (dm/get-prop vbox :x))
|
||||||
(scale gpu-state zoom zoom)
|
pan-y (- (dm/get-prop vbox :y))]
|
||||||
|
(draw-all-shapes internal-gpu-state zoom pan-x pan-y))))))
|
||||||
|
|
||||||
(let [x (dm/get-prop vbox :x)
|
(defn cancel-draw
|
||||||
y (dm/get-prop vbox :y)]
|
[frame-id]
|
||||||
(translate gpu-state (- x) (- y)))
|
(when (some? frame-id)
|
||||||
|
(js/cancelAnimationFrame frame-id)))
|
||||||
|
|
||||||
(run! (fn [shape]
|
(def ^:private canvas-options
|
||||||
(let [selrect (dm/get-prop shape :selrect)
|
|
||||||
x1 (dm/get-prop selrect :x1)
|
|
||||||
y1 (dm/get-prop selrect :y1)
|
|
||||||
x2 (dm/get-prop selrect :x2)
|
|
||||||
y2 (dm/get-prop selrect :y2)]
|
|
||||||
(draw-rect gpu-state x1 y1 x2 y2)))
|
|
||||||
(vals objects))
|
|
||||||
|
|
||||||
(flush gpu-state)))))
|
|
||||||
|
|
||||||
(def canvas-options
|
|
||||||
#js {:antialias true
|
#js {:antialias true
|
||||||
:depth true
|
:depth true
|
||||||
:stencil true
|
:stencil true
|
||||||
|
|
|
@ -181,10 +181,12 @@
|
||||||
[state name]
|
[state name]
|
||||||
(let [page-id (get state :current-page-id)
|
(let [page-id (get state :current-page-id)
|
||||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||||
result (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
|
result (or (d/seek (fn [shape] (= name (:name shape))) (vals objects))
|
||||||
(get objects (uuid/uuid name)))]
|
(get objects (uuid/uuid name)))]
|
||||||
(logjs name result)
|
#_(logjs name result)
|
||||||
nil))
|
result
|
||||||
|
|
||||||
|
#_nil))
|
||||||
|
|
||||||
(defn ^:export dump-object
|
(defn ^:export dump-object
|
||||||
[name]
|
[name]
|
||||||
|
|
|
@ -1,97 +1,15 @@
|
||||||
use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext};
|
pub mod render;
|
||||||
use std::boxed::Box;
|
pub mod shapes;
|
||||||
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
extern "C" {
|
use render::State;
|
||||||
pub fn emscripten_GetProcAddress(
|
|
||||||
name: *const ::std::os::raw::c_char,
|
|
||||||
) -> *const ::std::os::raw::c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GpuState {
|
|
||||||
context: DirectContext,
|
|
||||||
framebuffer_info: FramebufferInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct holds the state of the Rust application between JS calls.
|
|
||||||
///
|
|
||||||
/// It is created by [init] and passed to the other exported functions. Note that rust-skia data
|
|
||||||
/// structures are not thread safe, so a state must not be shared between different Web Workers.
|
|
||||||
pub struct State {
|
|
||||||
gpu_state: GpuState,
|
|
||||||
surface: skia::Surface,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
fn new(gpu_state: GpuState, surface: skia::Surface) -> Self {
|
|
||||||
State { gpu_state, surface }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_surface(&mut self, surface: skia::Surface) {
|
|
||||||
self.surface = surface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_gl() {
|
|
||||||
unsafe {
|
|
||||||
gl::load_with(|addr| {
|
|
||||||
let addr = std::ffi::CString::new(addr).unwrap();
|
|
||||||
emscripten_GetProcAddress(addr.into_raw() as *const _) as *const _
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This needs to be done once per WebGL context.
|
|
||||||
fn create_gpu_state() -> GpuState {
|
|
||||||
let interface = skia_safe::gpu::gl::Interface::new_native().unwrap();
|
|
||||||
let context = skia_safe::gpu::direct_contexts::make_gl(interface, None).unwrap();
|
|
||||||
let framebuffer_info = {
|
|
||||||
let mut fboid: gl::types::GLint = 0;
|
|
||||||
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
|
||||||
|
|
||||||
FramebufferInfo {
|
|
||||||
fboid: fboid.try_into().unwrap(),
|
|
||||||
format: skia_safe::gpu::gl::Format::RGBA8.into(),
|
|
||||||
protected: skia_safe::gpu::Protected::No,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
GpuState {
|
|
||||||
context,
|
|
||||||
framebuffer_info,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the Skia surface that will be used for rendering.
|
|
||||||
fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> skia::Surface {
|
|
||||||
let backend_render_target =
|
|
||||||
gpu::backend_render_targets::make_gl((width, height), 1, 8, gpu_state.framebuffer_info);
|
|
||||||
|
|
||||||
gpu::surfaces::wrap_backend_render_target(
|
|
||||||
&mut gpu_state.context,
|
|
||||||
&backend_render_target,
|
|
||||||
skia_safe::gpu::SurfaceOrigin::BottomLeft,
|
|
||||||
skia_safe::ColorType::RGBA8888,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color: skia::Color) {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
paint.set_color(color);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
surface.canvas().draw_rect(rect, &paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is called from JS after the WebGL context has been created.
|
/// This is called from JS after the WebGL context has been created.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn init(width: i32, height: i32) -> Box<State> {
|
pub extern "C" fn init(width: i32, height: i32) -> Box<render::State> {
|
||||||
let mut gpu_state = create_gpu_state();
|
let mut gpu_state = render::create_gpu_state();
|
||||||
let surface = create_surface(&mut gpu_state, width, height);
|
let surface = render::create_surface(&mut gpu_state, width, height);
|
||||||
|
|
||||||
let state = State::new(gpu_state, surface);
|
let state = State::new(gpu_state, surface);
|
||||||
|
|
||||||
|
@ -103,7 +21,7 @@ pub extern "C" fn init(width: i32, height: i32) -> Box<State> {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i32) {
|
pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i32) {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||||
let surface = create_surface(&mut state.gpu_state, width, height);
|
let surface = render::create_surface(&mut state.gpu_state, width, height);
|
||||||
state.set_surface(surface);
|
state.set_surface(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +31,20 @@ pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i
|
||||||
pub unsafe extern "C" fn draw_rect(state: *mut State, x1: f32, y1: f32, x2: f32, y2: f32) {
|
pub unsafe extern "C" fn draw_rect(state: *mut State, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||||
let r = skia::Rect::new(x1, y1, x2, y2);
|
let r = skia::Rect::new(x1, y1, x2, y2);
|
||||||
render_rect(&mut state.surface, r, skia::Color::RED);
|
render::render_rect(&mut state.surface, r, skia::Color::RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn draw_all_shapes(state: *mut State, zoom: f32, pan_x: f32, pan_y: f32) {
|
||||||
|
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
|
reset_canvas(state);
|
||||||
|
scale(state, zoom, zoom);
|
||||||
|
translate(state, pan_x, pan_y);
|
||||||
|
|
||||||
|
shapes::draw_all(state);
|
||||||
|
|
||||||
|
flush(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -143,6 +74,12 @@ pub unsafe extern "C" fn reset_canvas(state: *mut State) {
|
||||||
flush(state);
|
flush(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[no_mangle]
|
||||||
init_gl();
|
pub unsafe extern "C" fn shapes_buffer() -> *mut shapes::Shape {
|
||||||
|
let ptr = shapes::SHAPES_BUFFER.as_mut_ptr();
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
render::init_gl();
|
||||||
}
|
}
|
||||||
|
|
86
render-wasm/src/render.rs
Normal file
86
render-wasm/src/render.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use skia_safe as skia;
|
||||||
|
use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext};
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
pub fn emscripten_GetProcAddress(
|
||||||
|
name: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::std::os::raw::c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GpuState {
|
||||||
|
pub context: DirectContext,
|
||||||
|
framebuffer_info: FramebufferInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct holds the state of the Rust application between JS calls.
|
||||||
|
///
|
||||||
|
/// It is created by [init] and passed to the other exported functions. Note that rust-skia data
|
||||||
|
/// structures are not thread safe, so a state must not be shared between different Web Workers.
|
||||||
|
pub struct State {
|
||||||
|
pub gpu_state: GpuState,
|
||||||
|
pub surface: skia::Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(gpu_state: GpuState, surface: skia::Surface) -> Self {
|
||||||
|
State { gpu_state, surface }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_surface(&mut self, surface: skia::Surface) {
|
||||||
|
self.surface = surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_gl() {
|
||||||
|
unsafe {
|
||||||
|
gl::load_with(|addr| {
|
||||||
|
let addr = std::ffi::CString::new(addr).unwrap();
|
||||||
|
emscripten_GetProcAddress(addr.into_raw() as *const _) as *const _
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This needs to be done once per WebGL context.
|
||||||
|
pub(crate) fn create_gpu_state() -> GpuState {
|
||||||
|
let interface = skia_safe::gpu::gl::Interface::new_native().unwrap();
|
||||||
|
let context = skia_safe::gpu::direct_contexts::make_gl(interface, None).unwrap();
|
||||||
|
let framebuffer_info = {
|
||||||
|
let mut fboid: gl::types::GLint = 0;
|
||||||
|
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
||||||
|
|
||||||
|
FramebufferInfo {
|
||||||
|
fboid: fboid.try_into().unwrap(),
|
||||||
|
format: skia_safe::gpu::gl::Format::RGBA8.into(),
|
||||||
|
protected: skia_safe::gpu::Protected::No,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GpuState {
|
||||||
|
context,
|
||||||
|
framebuffer_info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the Skia surface that will be used for rendering.
|
||||||
|
pub(crate) fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> skia::Surface {
|
||||||
|
let backend_render_target =
|
||||||
|
gpu::backend_render_targets::make_gl((width, height), 1, 8, gpu_state.framebuffer_info);
|
||||||
|
|
||||||
|
gpu::surfaces::wrap_backend_render_target(
|
||||||
|
&mut gpu_state.context,
|
||||||
|
&backend_render_target,
|
||||||
|
skia_safe::gpu::SurfaceOrigin::BottomLeft,
|
||||||
|
skia_safe::ColorType::RGBA8888,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color: skia::Color) {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
|
paint.set_color(color);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
surface.canvas().draw_rect(rect, &paint);
|
||||||
|
}
|
31
render-wasm/src/shapes.rs
Normal file
31
render-wasm/src/shapes.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::render::{render_rect, State};
|
||||||
|
use skia_safe as skia;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Selrect {
|
||||||
|
pub x1: f32,
|
||||||
|
pub y1: f32,
|
||||||
|
pub x2: f32,
|
||||||
|
pub y2: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Shape = Selrect; // temp
|
||||||
|
|
||||||
|
pub static mut SHAPES_BUFFER: [Shape; 2048] = [Selrect {
|
||||||
|
x1: 0.0,
|
||||||
|
y1: 0.0,
|
||||||
|
x2: 0.0,
|
||||||
|
y2: 0.0,
|
||||||
|
}; 2048];
|
||||||
|
|
||||||
|
pub(crate) fn draw_all(state: &mut State) {
|
||||||
|
let shapes;
|
||||||
|
unsafe {
|
||||||
|
shapes = SHAPES_BUFFER.iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
for shape in shapes {
|
||||||
|
let r = skia::Rect::new(shape.x1, shape.y1, shape.x2, shape.y2);
|
||||||
|
render_rect(&mut state.surface, r, skia::Color::RED);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue