From 96bb282674dbdf192940e41f624343af6b2f9f90 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 29 Oct 2024 09:15:24 +0100 Subject: [PATCH] :sparkles: Fix many corner issues related to shape data structure change --- common/src/app/common/files/migrations.cljc | 2 +- common/src/app/common/geom/rect.cljc | 1 + .../app/common/geom/shapes/transforms.cljc | 1 - common/src/app/common/types/shape.cljc | 42 +++++------ common/src/app/common/types/shape/impl.cljc | 73 +++++++++---------- .../app/main/data/workspace/selection.cljs | 15 ++-- .../app/main/ui/workspace/shapes/common.cljs | 18 +---- .../src/app/main/ui/workspace/viewport.cljs | 22 +++--- frontend/src/app/render_wasm.cljs | 58 +++++++++++---- 9 files changed, 125 insertions(+), 107 deletions(-) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 2b6c4b450..2a0c0d184 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -499,7 +499,7 @@ object (-> object (update :selrect grc/make-rect) - (cts/map->Shape)))) + (cts/create-shape)))) (update-container [container] (d/update-when container :objects update-vals update-object))] (-> data diff --git a/common/src/app/common/geom/rect.cljc b/common/src/app/common/geom/rect.cljc index b18befe6c..b7d14d542 100644 --- a/common/src/app/common/geom/rect.cljc +++ b/common/src/app/common/geom/rect.cljc @@ -139,6 +139,7 @@ :width (mth/abs (- x2 x1)) :height (mth/abs (- y2 y1)))) + ;; FIXME: looks unused :position (let [x (dm/get-prop rect :x) y (dm/get-prop rect :y) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index c08f06781..079fe1de9 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -16,7 +16,6 @@ [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.path :as gpa] [app.common.math :as mth] - [app.common.record :as cr] [app.common.types.modifiers :as ctm])) #?(:clj (set! *warn-on-reflection* true)) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 707126c98..2d0e2ad94 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -6,7 +6,6 @@ (ns app.common.types.shape (:require - #?(:clj [app.common.fressian :as fres]) [app.common.colors :as clr] [app.common.data :as d] [app.common.geom.matrix :as gmt] @@ -14,10 +13,8 @@ [app.common.geom.proportions :as gpr] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] - [app.common.record :as cr] [app.common.schema :as sm] [app.common.schema.generators :as sg] - [app.common.transit :as t] [app.common.types.color :as ctc] [app.common.types.grid :as ctg] [app.common.types.plugins :as ctpg] @@ -33,8 +30,6 @@ [app.common.uuid :as uuid] [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? [o] (impl/shape? o)) @@ -453,27 +448,30 @@ ;; NOTE: used for create ephimeral shapes for multiple selection :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 [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 - (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 - (impl/map->Shape)))) + (impl/create-shape attrs))) (defn setup-rect "Initializes the selrect and points for a shape." diff --git a/common/src/app/common/types/shape/impl.cljc b/common/src/app/common/types/shape/impl.cljc index 331b4e99d..11eade8a8 100644 --- a/common/src/app/common/types/shape/impl.cljc +++ b/common/src/app/common/types/shape/impl.cljc @@ -7,18 +7,14 @@ (ns app.common.types.shape.impl (:require #?(:clj [app.common.fressian :as fres]) - [app.common.colors :as clr] - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.geom.rect :as grc] + #?(: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.schema :as sm] - [app.common.schema.generators :as sg] [app.common.transit :as t] - [app.common.uuid :as uuid] - [clojure.core :as c] - [clojure.set :as set] - [cuerdas.core :as str])) + [clojure.core :as c])) + +(def enabled-wasm-ready-shape false) #?(:cljs (do @@ -52,11 +48,11 @@ ;; (ShapeWithBuffer. bf32 delegate))) IWithMeta - (-with-meta [coll meta] + (-with-meta [_ meta] (ShapeWithBuffer. buffer (with-meta delegate meta))) IMeta - (-meta [coll] (meta delegate)) + (-meta [_] (meta delegate)) ICollection (-conj [coll entry] @@ -77,20 +73,20 @@ (seq delegate))) ICounted - (-count [coll] + (-count [_] (+ 1 (count delegate))) ILookup (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] + (-lookup [_ k not-found] (if (= k :selrect) (read-selrect buffer) (c/-lookup delegate k not-found))) IFind - (-find [coll k] + (-find [_ k] (if (= k :selrect) (c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry (c/-find delegate k))) @@ -99,7 +95,7 @@ (-assoc [coll k v] (impl-assoc coll k v)) - (-contains-key? [coll k] + (-contains-key? [_ k] (or (= k :selrect) (contains? delegate k))) @@ -115,13 +111,14 @@ (-lookup coll k not-found)) IPrintWithWriter - (-pr-writer [coll writer opts] + (-pr-writer [_ writer _] (-write writer (str "#penpot/shape " (:id delegate)))))) (defn shape? [o] - (or (instance? Shape o) - #?(:cljs (instance? ShapeWithBuffer o)))) + #?(:clj (instance? Shape o) + :cljs (or (instance? Shape o) + (instance? ShapeWithBuffer o)))) ;; --- SHAPE IMPL @@ -194,15 +191,18 @@ (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)) -#?(:cljs - (defn create-shape - "Instanciate a shape from a map" - [data] - (let [selrect (:selrect data) - buffer (new Float32Array 4)] - (write-selrect buffer selrect) - (ShapeWithBuffer. buffer (dissoc data :selrect))))) + :clj (map->Shape attrs))) ;; --- SHAPE SERIALIZATION @@ -210,21 +210,18 @@ {:id "shape" :class Shape :wfn #(into {} %) - :rfn #?(:cljs create-shape - :clj map->Shape)}) - -#?(:cljs (t/add-handlers! - {:id "shape" - :class ShapeWithBuffer - :wfn #(into {} %) - :rfn #?(:cljs create-shape - :clj map->Shape)})) - + :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 map->Shape fres/read-map-like)})) + :rfn (comp create-shape fres/read-map-like)})) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index f0210cf53..8150ea465 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -15,7 +15,6 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.logic.libraries :as cll] - [app.common.record :as cr] [app.common.types.component :as ctk] [app.common.uuid :as uuid] [app.main.data.changes :as dch] @@ -68,15 +67,15 @@ calculate-selrect (fn [selrect [delta space?]] - (let [selrect (-> (cr/clone selrect) - (cr/update! :x2 + (:x delta)) - (cr/update! :y2 + (:y delta))) + (let [selrect (-> selrect + (update :x2 + (:x delta)) + (update :y2 + (:y delta))) selrect (if ^boolean space? - (-> (cr/clone selrect) - (cr/update! :x1 + (:x delta)) - (cr/update! :y1 + (:y delta))) + (-> selrect + (update :x1 + (:x delta)) + (update :y1 + (:y delta))) selrect)] - (grc/update-rect! selrect :corners))) + (grc/update-rect selrect :corners))) selrect-stream (->> ms/mouse-position diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index 4af042e87..e09092ca7 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -6,32 +6,20 @@ (ns app.main.ui.workspace.shapes.common (:require - [app.common.record :as cr] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.debug :as wsd] [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 [np op] - (check-shape (unchecked-get np "shape") - (unchecked-get op "shape"))) + (= (unchecked-get np "shape") + (unchecked-get op "shape"))) (defn generic-wrapper-factory [component] (mf/fnc generic-wrapper {::mf/wrap [#(mf/memo' % check-shape-props)] - ::mf/wrap-props false} + ::mf/props :obj} [props] (let [shape (unchecked-get props "shape")] [:> shape-container {:shape shape} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 1170068a2..fb3eb79b3 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -261,7 +261,8 @@ (= (:layout selected-frame) :flex) (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? (:x first-shape) @@ -276,19 +277,20 @@ (when ^boolean render.wasm/enabled? (mf/with-effect [] - (time (when-let [canvas (mf/ref-val canvas-ref)] - (->> render.wasm/module - (p/fmap (fn [ready?] - (when ready? - (reset! canvas-init? true) - (render.wasm/assign-canvas canvas))))) - (fn [] - (render.wasm/clear-canvas))))) + (when-let [canvas (mf/ref-val canvas-ref)] + (->> render.wasm/module + (p/fmap (fn [ready?] + (when ready? + (reset! canvas-init? true) + (render.wasm/assign-canvas canvas))))) + (fn [] + (render.wasm/clear-canvas)))) (mf/with-effect [objects-modified canvas-init?] (when @canvas-init? (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)))] @@ -300,6 +302,8 @@ (hooks/setup-keyboard alt? mod? space? z? shift?) (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?) + + ;; FIXME: this should be removed on canvas viewport (hooks/setup-viewport-modifiers modifiers base-objects) (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) diff --git a/frontend/src/app/render_wasm.cljs b/frontend/src/app/render_wasm.cljs index 045d6973b..44086efd7 100644 --- a/frontend/src/app/render_wasm.cljs +++ b/frontend/src/app/render_wasm.cljs @@ -9,26 +9,58 @@ (:require [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] + [app.common.types.shape.impl] [app.config :as cf] [promesa.core :as p])) (def enabled? (contains? cf/flags :render-wasm)) -(defonce ^:dynamic internal-module #js {}) -(defonce ^:dynamic internal-gpu-state #js {}) +(set! app.common.types.shape.impl/enabled-wasm-ready-shape enabled?) -(defn set-objects [objects] - (let [shapes-buffer (unchecked-get internal-module "_shapes_buffer") - heap (unchecked-get internal-module "HEAPF32") - ;; size *in bytes* for each shapes::Shape - rect-size 16 - ;; TODO: remove the `take` once we have the dynamic data structure in Rust - supported-shapes (take 2048 (filter #(not (cfh/root? %)) (vals objects))) - mem (js/Float32Array. (.-buffer heap) (shapes-buffer) (* rect-size (count supported-shapes)))] - (run! (fn [[shape index]] - (.set mem (.-buffer shape) (* index rect-size))) - (zipmap supported-shapes (range))))) +(defonce internal-module #js {}) +(defonce internal-gpu-state #js {}) + +;; 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]