Fix many corner issues related to shape data structure change

This commit is contained in:
Alejandro Alonso 2024-10-29 09:15:24 +01:00 committed by Andrey Antukh
parent 4623f36042
commit 96bb282674
9 changed files with 125 additions and 107 deletions

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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,10 +13,8 @@
[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]
@ -33,8 +30,6 @@
[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]
(impl/shape? o)) (impl/shape? o))
@ -453,27 +448,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
(cond-> attrs
(and (not= :path type) (and (not= :path type)
(not= :bool type)) (not= :bool type))
(-> (assoc :x 0) (-> (assoc :x 0)
(assoc :y 0) (assoc :y 0)
(assoc :width 0.01) (assoc :width 0.01)
(assoc :height 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))]
:always (impl/create-shape attrs)))
(assoc :id (uuid/next)
:frame-id uuid/zero
:parent-id uuid/zero
:rotation 0)
:always
(impl/map->Shape))))
(defn setup-rect (defn setup-rect
"Initializes the selrect and points for a shape." "Initializes the selrect and points for a shape."

View file

@ -7,18 +7,14 @@
(ns app.common.types.shape.impl (ns app.common.types.shape.impl
(:require (:require
#?(:clj [app.common.fressian :as fres]) #?(:clj [app.common.fressian :as fres])
[app.common.colors :as clr] #?(:cljs [app.common.data.macros :as dm])
[app.common.data :as d] #?(:cljs [app.common.geom.rect :as grc])
[app.common.data.macros :as dm] #?(:cljs [cuerdas.core :as str])
[app.common.geom.rect :as grc]
[app.common.record :as cr] [app.common.record :as cr]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.uuid :as uuid] [clojure.core :as c]))
[clojure.core :as c]
[clojure.set :as set] (def enabled-wasm-ready-shape false)
[cuerdas.core :as str]))
#?(:cljs #?(:cljs
(do (do
@ -52,11 +48,11 @@
;; (ShapeWithBuffer. bf32 delegate))) ;; (ShapeWithBuffer. bf32 delegate)))
IWithMeta IWithMeta
(-with-meta [coll meta] (-with-meta [_ meta]
(ShapeWithBuffer. buffer (with-meta delegate meta))) (ShapeWithBuffer. buffer (with-meta delegate meta)))
IMeta IMeta
(-meta [coll] (meta delegate)) (-meta [_] (meta delegate))
ICollection ICollection
(-conj [coll entry] (-conj [coll entry]
@ -77,20 +73,20 @@
(seq delegate))) (seq delegate)))
ICounted ICounted
(-count [coll] (-count [_]
(+ 1 (count delegate))) (+ 1 (count delegate)))
ILookup ILookup
(-lookup [coll k] (-lookup [coll k]
(-lookup coll k nil)) (-lookup coll k nil))
(-lookup [coll k not-found] (-lookup [_ k not-found]
(if (= k :selrect) (if (= k :selrect)
(read-selrect buffer) (read-selrect buffer)
(c/-lookup delegate k not-found))) (c/-lookup delegate k not-found)))
IFind IFind
(-find [coll k] (-find [_ k]
(if (= k :selrect) (if (= k :selrect)
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry (c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
(c/-find delegate k))) (c/-find delegate k)))
@ -99,7 +95,7 @@
(-assoc [coll k v] (-assoc [coll k v]
(impl-assoc coll k v)) (impl-assoc coll k v))
(-contains-key? [coll k] (-contains-key? [_ k]
(or (= k :selrect) (or (= k :selrect)
(contains? delegate k))) (contains? delegate k)))
@ -115,13 +111,14 @@
(-lookup coll k not-found)) (-lookup coll k not-found))
IPrintWithWriter IPrintWithWriter
(-pr-writer [coll writer opts] (-pr-writer [_ writer _]
(-write writer (str "#penpot/shape " (:id delegate)))))) (-write writer (str "#penpot/shape " (:id delegate))))))
(defn shape? (defn shape?
[o] [o]
(or (instance? Shape o) #?(:clj (instance? Shape o)
#?(:cljs (instance? ShapeWithBuffer o)))) :cljs (or (instance? Shape o)
(instance? ShapeWithBuffer o))))
;; --- SHAPE IMPL ;; --- SHAPE IMPL
@ -194,15 +191,18 @@
(next es)) (next es))
(throw (js/Error. "conj on a map takes map entries or seqables of map entries"))))))))) (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))))
(defn create-shape
#?(:cljs
(defn create-shape
"Instanciate a shape from a map" "Instanciate a shape from a map"
[data] [attrs]
(let [selrect (:selrect data) #?(:cljs
(if enabled-wasm-ready-shape
(let [selrect (:selrect attrs)
buffer (new Float32Array 4)] buffer (new Float32Array 4)]
(write-selrect buffer selrect) (write-selrect buffer selrect)
(ShapeWithBuffer. buffer (dissoc data :selrect))))) (ShapeWithBuffer. buffer (dissoc attrs :selrect)))
(map->Shape attrs))
:clj (map->Shape attrs)))
;; --- SHAPE SERIALIZATION ;; --- SHAPE SERIALIZATION
@ -210,21 +210,18 @@
{:id "shape" {:id "shape"
:class Shape :class Shape
:wfn #(into {} %) :wfn #(into {} %)
:rfn #?(:cljs create-shape :rfn create-shape})
:clj map->Shape)})
#?(:cljs (t/add-handlers! #?(:cljs
(t/add-handlers!
{:id "shape" {:id "shape"
:class ShapeWithBuffer :class ShapeWithBuffer
:wfn #(into {} %) :wfn #(into {} %)
:rfn #?(:cljs create-shape :rfn create-shape}))
:clj map->Shape)}))
#?(:clj #?(:clj
(fres/add-handlers! (fres/add-handlers!
{:name "penpot/shape" {:name "penpot/shape"
:class Shape :class Shape
:wfn fres/write-map-like :wfn fres/write-map-like
:rfn (comp map->Shape fres/read-map-like)})) :rfn (comp create-shape fres/read-map-like)}))

View file

@ -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?
(-> (cr/clone 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

View file

@ -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}

View file

@ -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)
@ -276,19 +277,20 @@
(when ^boolean render.wasm/enabled? (when ^boolean render.wasm/enabled?
(mf/with-effect [] (mf/with-effect []
(time (when-let [canvas (mf/ref-val canvas-ref)] (when-let [canvas (mf/ref-val canvas-ref)]
(->> render.wasm/module (->> render.wasm/module
(p/fmap (fn [ready?] (p/fmap (fn [ready?]
(when ready? (when ready?
(reset! 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 [objects-modified canvas-init?] (mf/with-effect [objects-modified canvas-init?]
(when @canvas-init? (when @canvas-init?
(render.wasm/set-objects objects-modified) (render.wasm/set-objects objects-modified)
(render.wasm/draw-objects zoom vbox))) (render.wasm/draw-objects zoom vbox)))
(mf/with-effect [vbox canvas-init?] (mf/with-effect [vbox canvas-init?]
(let [frame-id (when @canvas-init? (do (let [frame-id (when @canvas-init? (do
(render.wasm/draw-objects zoom vbox)))] (render.wasm/draw-objects zoom vbox)))]
@ -300,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)

View file

@ -9,26 +9,58 @@
(:require (:require
[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.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 set-objects [objects] (defonce internal-module #js {})
(let [shapes-buffer (unchecked-get internal-module "_shapes_buffer") (defonce internal-gpu-state #js {})
heap (unchecked-get internal-module "HEAPF32")
;; size *in bytes* for each shapes::Shape ;; TODO: remove the `take` once we have the dynamic data structure in Rust
rect-size 16 (def xform
;; TODO: remove the `take` once we have the dynamic data structure in Rust (comp
supported-shapes (take 2048 (filter #(not (cfh/root? %)) (vals objects))) (remove cfh/root?)
mem (js/Float32Array. (.-buffer heap) (shapes-buffer) (* rect-size (count supported-shapes)))] (take 2048)))
(run! (fn [[shape index]]
(.set mem (.-buffer shape) (* index rect-size))) ;; Size in number of f32 values that represents the shape selrect (
(zipmap supported-shapes (range))))) (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 (defn draw-objects
[zoom vbox] [zoom vbox]