0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-05 11:31:35 -05:00

Merge pull request #2733 from penpot/alotor-performance

Performance enhancements
This commit is contained in:
Alejandro 2023-01-05 07:39:57 +01:00 committed by GitHub
commit b273bd44c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 398 additions and 216 deletions

View file

@ -170,8 +170,7 @@
(dm/get-prop p2 :x))
dy (- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))]
(mth/sqrt (+ (mth/pow dx 2)
(mth/pow dy 2)))))
(mth/hypot dx dy)))
(defn distance-vector
"Calculate the distance, separated x and y."
@ -191,8 +190,7 @@
(assert (point? pt) "point instance expected")
(let [x (dm/get-prop pt :x)
y (dm/get-prop pt :y)]
(mth/sqrt (+ (mth/pow x 2)
(mth/pow y 2)))))
(mth/hypot x y)))
(defn angle
"Returns the smaller angle between two vectors.

View file

@ -28,6 +28,16 @@
;; [(get-in objects [k :name]) v]))
;; modif-tree))))
(defn children-sequence
"Given an id returns a sequence of its children"
[id objects]
(->> (tree-seq
#(d/not-empty? (dm/get-in objects [% :shapes]))
#(dm/get-in objects [% :shapes])
id)
(map #(get objects %))))
(defn resolve-tree-sequence
"Given the ids that have changed search for layout roots to recalculate"
[ids objects]
@ -75,20 +85,12 @@
(cond-> result
(not contains-parent?)
(conj root)))))
(generate-tree ;; Generate a tree sequence from a given root id
[id]
(->> (tree-seq
#(d/not-empty? (dm/get-in objects [% :shapes]))
#(dm/get-in objects [% :shapes])
id)
(map #(get objects %))))]
(conj root)))))]
(let [roots (->> ids (reduce calculate-common-roots #{}))]
(concat
(when (contains? ids uuid/zero) [(get objects uuid/zero)])
(mapcat generate-tree roots)))))
(mapcat #(children-sequence % objects) roots)))))
(defn- set-children-modifiers
"Propagates the modifiers from a parent too its children applying constraints if necesary"
@ -296,28 +298,88 @@
result (assoc result (:id shape) new-bounds)]
(recur result (rest shapes)))))))
(defn reflow-layout
[objects old-modif-tree bounds ignore-constraints id]
(let [tree-seq (children-sequence id objects)
[modif-tree _]
(reduce
#(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}]
tree-seq)
bounds (transform-bounds bounds objects modif-tree tree-seq)
modif-tree (merge-modif-tree old-modif-tree modif-tree)]
[modif-tree bounds]))
(defn sizing-auto-modifiers
"Recalculates the layouts to adjust the sizing: auto new sizes"
[modif-tree sizing-auto-layouts objects bounds ignore-constraints]
(loop [modif-tree modif-tree
bounds bounds
sizing-auto-layouts (reverse sizing-auto-layouts)]
(if-let [current (first sizing-auto-layouts)]
(let [parent-base (get objects current)
(let [;; Step-1 resize the auto-width/height. Reflow the parents if they are also auto-width/height
[modif-tree bounds to-reflow]
(loop [modif-tree modif-tree
bounds bounds
sizing-auto-layouts (reverse sizing-auto-layouts)
to-reflow #{}]
(if-let [current (first sizing-auto-layouts)]
(let [parent-base (get objects current)
resize-modif-tree
{current {:modifiers (calc-auto-modifiers objects bounds parent-base)}}
[modif-tree bounds]
(if (contains? to-reflow current)
(reflow-layout objects modif-tree bounds ignore-constraints current)
[modif-tree bounds])
tree-seq (resolve-tree-sequence #{current} objects)
auto-resize-modifiers
(calc-auto-modifiers objects bounds parent-base)
[resize-modif-tree _]
(reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq)
to-reflow
(cond-> to-reflow
(contains? to-reflow current)
(disj current))]
bounds (transform-bounds bounds objects resize-modif-tree tree-seq)
(if (ctm/empty? auto-resize-modifiers)
(recur modif-tree
bounds
(rest sizing-auto-layouts)
to-reflow)
modif-tree (merge-modif-tree modif-tree resize-modif-tree)]
(recur modif-tree bounds (rest sizing-auto-layouts)))
modif-tree)))
(let [resize-modif-tree {current {:modifiers auto-resize-modifiers}}
tree-seq (children-sequence current objects)
[resize-modif-tree _]
(reduce
#(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}]
tree-seq)
bounds (transform-bounds bounds objects resize-modif-tree tree-seq)
modif-tree (merge-modif-tree modif-tree resize-modif-tree)
to-reflow
(cond-> to-reflow
(and (ctl/layout-child-id? objects current)
(not= uuid/zero (:frame-id parent-base)))
(conj (:frame-id parent-base)))]
(recur modif-tree
bounds
(rest sizing-auto-layouts)
to-reflow))))
[modif-tree bounds to-reflow]))
;; Step-2: After resizing we still need to reflow the layout parents that are not auto-width/height
tree-seq (resolve-tree-sequence to-reflow objects)
[reflow-modif-tree _]
(reduce
#(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}]
tree-seq)
result (merge-modif-tree modif-tree reflow-modif-tree)]
result))
(defn set-objects-modifiers
([modif-tree objects ignore-constraints snap-pixel?]

View file

@ -117,7 +117,7 @@
[x y] (->> coords (mapv solve-derivative))
;; normalize value
d (mth/sqrt (+ (* x x) (* y y)))]
d (mth/hypot x y)]
(if (mth/almost-zero? d)
(gpt/point 0 0)

View file

@ -139,12 +139,18 @@
#?(:cljs (math/toDegrees radians)
:clj (Math/toDegrees radians)))
(defn hypot
"Square root of the squares addition"
[a b]
#?(:cljs (js/Math.hypot a b)
:clj (Math/hypot a b)))
(defn distance
"Calculate the distance between two points."
[[x1 y1] [x2 y2]]
(let [dx (- x1 x2)
dy (- y1 y2)]
(-> (sqrt (+ (pow dx 2) (pow dy 2)))
(-> (hypot dx dy)
(precision 2))))
(defn log10
@ -182,3 +188,4 @@
"Get the sign (+1 / -1) for the number"
[n]
(if (neg? n) -1 1))

View file

@ -100,15 +100,13 @@
(and (= :frame (:type shape)) (= :flex (:layout shape)))))
(defn layout-child? [objects shape]
(let [parent-id (:parent-id shape)
parent (get objects parent-id)]
(layout? parent)))
(let [frame-id (:frame-id shape)
frame (get objects frame-id)]
(layout? frame)))
(defn layout-child-id? [objects id]
(let [shape (get objects id)
parent-id (:parent-id shape)
parent (get objects parent-id)]
(layout? parent)))
(let [shape (get objects id)]
(layout-child? objects shape)))
(defn inside-layout?
"Check if the shape is inside a layout"

View file

@ -37,19 +37,21 @@
(assoc :x (- (:x point) (* sx (- dy dx)))))))
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?]
(let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
shape-rect (gsh/make-rect x y width height)
(if (and (some? x) (some? y) (some? width) (some? height))
(let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
shape-rect (gsh/make-rect x y width height)
scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
(/ (:height draw-rect) (:height shape-rect)))
scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
(/ (:height draw-rect) (:height shape-rect)))
movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
(-> shape
(assoc :click-draw? false)
(gsh/transform-shape (-> (ctm/empty)
(ctm/resize scalev (gpt/point x y))
(ctm/move movev))))))
(-> shape
(assoc :click-draw? false)
(gsh/transform-shape (-> (ctm/empty)
(ctm/resize scalev (gpt/point x y))
(ctm/move movev)))))
shape))
(defn update-drawing [state initial point lock?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock?))

View file

@ -415,10 +415,14 @@
[id]
(ptk/reify ::clean-text-modifier
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/of #(update % :workspace-text-modifier dissoc id))
;; We delay a bit the change so there is no weird transition to the user
(rx/delay 50)))))
(watch [_ state _]
(let [current-value (dm/get-in state [:workspace-text-modifier id])]
;; We only dissocc the value when hasn't change after a time
(->> (rx/of (fn [state]
(cond-> state
(identical? (dm/get-in state [:workspace-text-modifier id]) current-value)
(update :workspace-text-modifier dissoc id))))
(rx/delay 100))))))
(defn remove-text-modifier
[id]

View file

@ -219,6 +219,7 @@
(rx/concat
(->> 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]]
@ -600,9 +601,17 @@
move-events (->> stream
(rx/filter (ptk/type? ::nudge-selected-shapes))
(rx/filter #(= direction (deref %))))
stopper (->> move-events
(rx/debounce 100)
(rx/take 1))
stopper
(->> move-events
;; We stop when there's been 1s without movement or after 250ms after a key-up
(rx/switch-map #(rx/merge
(rx/timer 1000)
(->> stream
(rx/filter ms/key-up?)
(rx/delay 250))))
(rx/take 1))
scale (if shift? (gpt/point (or (:big nudge) 10)) (gpt/point (or (:small nudge) 1)))
mov-vec (gpt/multiply (get-displacement direction) scale)]

View file

@ -324,11 +324,7 @@
(l/derived :workspace-editor-state st/state))
(def workspace-modifiers
(l/derived :workspace-modifiers st/state))
(defn workspace-modifiers-by-id
[ids]
(l/derived #(select-keys % ids) workspace-modifiers))
(l/derived :workspace-modifiers st/state =))
(def workspace-modifiers-with-objects
(l/derived
@ -340,20 +336,29 @@
(and (= (:modifiers a) (:modifiers b))
(identical? (:objects a) (:objects b))))))
(defn workspace-modifiers-by-frame-id
[frame-id]
(def workspace-frame-modifiers
(l/derived
(fn [{:keys [modifiers objects]}]
(let [keys (->> modifiers
(keys)
(filter (fn [id]
(let [shape (get objects id)]
(or (= frame-id id)
(and (= frame-id (:frame-id shape))
(not (= :frame (:type shape)))))))))]
(select-keys modifiers keys)))
workspace-modifiers-with-objects
=))
(->> modifiers
(reduce
(fn [result [id modifiers]]
(let [shape (get objects id)
frame-id (:frame-id shape)]
(cond
(cph/frame-shape? shape)
(assoc-in result [id id] modifiers)
(some? frame-id)
(assoc-in result [frame-id id] modifiers)
:else
result)))
{})))
workspace-modifiers-with-objects))
(defn workspace-modifiers-by-frame-id
[frame-id]
(l/derived #(get % frame-id) workspace-frame-modifiers =))
(defn select-bool-children [id]
(l/derived (partial wsh/select-bool-children id) st/state =))

View file

@ -21,6 +21,16 @@
[v]
(instance? KeyboardEvent v))
(defn key-up?
[v]
(and (keyboard-event? v)
(= :up (:type v))))
(defn key-down?
[v]
(and (keyboard-event? v)
(= :down (:type v))))
(defrecord MouseEvent [type ctrl shift alt meta])
(defn mouse-event?

View file

@ -110,9 +110,7 @@
:circle [:> circle-wrapper opts]
:svg-raw [:> svg-raw-wrapper opts]
:bool [:> bool-wrapper opts]
;; Only used when drawing a new frame.
:frame [:> nested-frame-wrapper opts]
:frame [:> nested-frame-wrapper opts]
nil)])))

View file

@ -30,29 +30,16 @@
[promesa.core :as p]
[rumext.v2 :as mf]))
(defn strip-position-data [shape]
(-> shape
(cond-> (some? (meta (:position-data shape)))
(with-meta (meta (:position-data shape))))
(dissoc :position-data)))
(defn fix-position [shape modifier]
(let [shape' (gsh/transform-shape shape modifier)
(defn fix-position [shape]
(let [modifiers (:modifiers shape)
shape' (gsh/transform-shape shape modifiers)
;; We need to remove the movement because the dynamic modifiers will have move it
deltav (gpt/to-vec (gpt/point (:selrect shape'))
(gpt/point (:selrect shape)))]
(-> shape
(gsh/transform-shape (ctm/move modifier deltav))
(mdwm/update-grow-type shape))))
(defn process-shape [modifiers {:keys [id] :as shape}]
(let [modifier (dm/get-in modifiers [id :modifiers])]
(-> shape
(cond-> (and (some? modifier) (not (ctm/only-move? modifier)))
(fix-position modifier))
(cond-> (nil? (:position-data shape))
(assoc :migrate true))
strip-position-data)))
(gsh/transform-shape (ctm/move modifiers deltav))
(mdwm/update-grow-type shape)
(dissoc :modifiers))))
(defn- update-with-editor-state
"Updates the shape with the current state in the editor"
@ -87,6 +74,7 @@
(not (mth/almost-zero? height))
(not migrate))
(st/emit! (dwt/resize-text id width height)))))
(st/emit! (dwt/clean-text-modifier id))))
(defn- update-text-modifier
@ -136,8 +124,8 @@
(or (identical? shape other)
(and
;; Check if both shapes are equivalent removing their geometry data
(= (dissoc shape :migrate :points :selrect :height :width :x :y)
(dissoc other :migrate :points :selrect :height :width :x :y))
(= (dissoc shape :migrate :points :selrect :height :width :x :y :position-data :modifiers)
(dissoc other :migrate :points :selrect :height :width :x :y :position-data :modifiers))
;; Check if the position and size is close. If any of these changes the shape has changed
;; and if not there is no geometry relevant change
@ -146,55 +134,69 @@
(mth/close? (:width shape) (:width other))
(mth/close? (:height shape) (:height other)))))
(mf/defc viewport-texts-wrapper
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
(mf/defc text-changes-renderer
{::mf/wrap-props false}
[props]
(let [text-shapes (obj/get props "text-shapes")
modifiers (obj/get props "modifiers")
prev-modifiers (hooks/use-previous modifiers)
prev-text-shapes (hooks/use-previous text-shapes)
;; A change in position-data won't be a "real" change
text-change?
(fn [id]
(let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id)
old-modifiers (ctm/select-geometry (get prev-modifiers id))
new-modifiers (ctm/select-geometry (get modifiers id))
remote? (some? (-> new-shape meta :session-id))]
(or (and (not remote?)
(or (and (not remote?) ;; changes caused by a remote peer are not re-calculated
(not (text-properties-equal? old-shape new-shape)))
(and (not= new-modifiers old-modifiers)
(or (ctm/empty? new-modifiers)
(ctm/empty? old-modifiers)))
(and (not= new-modifiers old-modifiers)
(or (not (ctm/only-move? new-modifiers))
(not (ctm/only-move? old-modifiers))))
;; When the position data is nil we force to recalculate
(:migrate new-shape))))
(nil? (:position-data new-shape)))))
changed-texts
(mf/use-memo
(mf/deps text-shapes modifiers)
(mf/deps text-shapes)
#(->> (keys text-shapes)
(filter text-change?)
(map (d/getf text-shapes))))
handle-update-modifier (mf/use-callback update-text-modifier)
handle-update-shape (mf/use-callback update-text-shape)]
[:*
[:.text-changes-renderer
(for [{:keys [id] :as shape} changed-texts]
[:& text-container {:shape shape
:on-update (if (some? (get modifiers (:id shape)))
handle-update-modifier
handle-update-shape)
:key (str (dm/str "text-container-" id))}])]))
[:& text-container {:key (str (dm/str "text-container-" id))
:shape shape
:on-update handle-update-shape}])]))
(mf/defc text-modifiers-renderer
{::mf/wrap-props false}
[props]
(let [text-shapes (-> (obj/get props "text-shapes")
(update-vals fix-position))
prev-text-shapes (hooks/use-previous text-shapes)
text-change?
(fn [id]
(let [new-shape (get text-shapes id)
old-shape (get prev-text-shapes id)]
(and
(some? new-shape)
(some? old-shape)
(not (text-properties-equal? old-shape new-shape)))))
changed-texts
(mf/use-memo
(mf/deps text-shapes)
#(->> (keys text-shapes)
(filter text-change?)
(map (d/getf text-shapes))))
handle-update-shape (mf/use-callback update-text-modifier)]
[:.text-changes-renderer
(for [{:keys [id] :as shape} changed-texts]
[:& text-container {:key (str (dm/str "text-container-" id))
:shape shape
:on-update handle-update-shape}])]))
(mf/defc viewport-text-editing
{::mf/wrap-props false}
@ -256,21 +258,30 @@
text-shapes
(mf/use-memo
(mf/deps objects)
#(into {} (filter (comp cph/text-shape? second)) objects))
(fn []
(into {} (filter (comp cph/text-shape? second)) objects)))
text-shapes
(mf/use-memo
(mf/deps text-shapes modifiers)
#(update-vals text-shapes (partial process-shape modifiers)))
(hooks/use-equal-memo text-shapes)
editing-shape (get text-shapes edition)
;; This memo is necessary so the viewport-text-wrapper memoize its props correctly
text-shapes-wrapper
text-shapes-changes
(mf/use-memo
(mf/deps text-shapes edition)
(fn []
(dissoc text-shapes edition)))]
(-> text-shapes
(dissoc edition))))
text-shapes-modifiers
(mf/use-memo
(mf/deps modifiers text-shapes)
(fn []
(into {}
(keep (fn [[id modifiers]]
(when-let [shape (get text-shapes id)]
(vector id (merge shape modifiers)))))
modifiers)))]
;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are
;; edited
@ -284,5 +295,5 @@
(when editing-shape
[:& viewport-text-editing {:shape editing-shape}])
[:& viewport-texts-wrapper {:text-shapes text-shapes-wrapper
:modifiers modifiers}]]))
[:& text-modifiers-renderer {:text-shapes text-shapes-modifiers}]
[:& text-changes-renderer {:text-shapes text-shapes-changes}]]))

View file

@ -28,28 +28,40 @@
has-value? (not (nil? blur))
multiple? (= blur :multiple)
change! (fn [update-fn] (st/emit! (dch/update-shapes ids update-fn)))
change!
(mf/use-callback
(mf/deps ids)
(fn [update-fn]
(st/emit! (dch/update-shapes ids update-fn))))
handle-add
(fn []
(change! #(assoc % :blur (create-blur))))
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(assoc % :blur (create-blur)))))
handle-delete
(fn []
(change! #(dissoc % :blur)))
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(dissoc % :blur))))
handle-change
(fn [value]
(change! #(cond-> %
(not (contains? % :blur))
(assoc :blur (create-blur))
(mf/use-callback
(mf/deps change!)
(fn [value]
(change! #(cond-> %
(not (contains? % :blur))
(assoc :blur (create-blur))
:always
(assoc-in [:blur :value] value))))
:always
(assoc-in [:blur :value] value)))))
handle-toggle-visibility
(fn []
(change! #(update-in % [:blur :hidden] not)))]
(mf/use-callback
(mf/deps change!)
(fn []
(change! #(update-in % [:blur :hidden] not))))]
[:div.element-set
[:div.element-set-title

View file

@ -12,6 +12,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.modifiers :as dwm]
[app.main.refs :as refs]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as ui-hooks]
@ -40,6 +41,7 @@
[app.main.ui.workspace.viewport.snap-distances :as snap-distances]
[app.main.ui.workspace.viewport.snap-points :as snap-points]
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
[beicon.core :as rx]
[debug :refer [debug?]]
@ -47,6 +49,20 @@
;; --- Viewport
(defn apply-modifiers-to-selected
[selected objects text-modifiers modifiers]
(into []
(comp
(keep (d/getf objects))
(map (fn [{:keys [id] :as shape}]
(cond-> shape
(and (cph/text-shape? shape) (contains? text-modifiers id))
(dwm/apply-text-modifier (get text-modifiers id))
(contains? modifiers id)
(gsh/transform-shape (dm/get-in modifiers [id :modifiers]))))))
selected))
(mf/defc viewport
[{:keys [wlocal wglobal selected layout file] :as props}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
@ -79,6 +95,7 @@
base-objects (-> objects (ui-hooks/with-focus-objects focus))
modifiers (mf/deref refs/workspace-modifiers)
text-modifiers (mf/deref refs/workspace-text-modifier)
objects-modified (mf/with-memo [base-objects modifiers]
(gsh/apply-objects-modifiers base-objects modifiers selected))
@ -98,7 +115,8 @@
active-frames (mf/use-state #{})
;; REFS
viewport-ref (mf/use-ref nil)
[viewport-ref
on-viewport-ref] (create-viewport-ref)
;; VARS
disable-paste (mf/use-var false)
@ -118,7 +136,8 @@
drawing-tool (:tool drawing)
drawing-obj (:object drawing)
selected-shapes (into [] (keep (d/getf objects-modified)) selected)
selected-shapes (apply-modifiers-to-selected selected base-objects text-modifiers modifiers)
selected-frames (into #{} (map :frame-id) selected-shapes)
;; Only when we have all the selected shapes in one frame
@ -140,15 +159,14 @@
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition workspace-read-only?)
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file viewport-ref zoom)
on-drop (actions/on-drop file)
on-mouse-down (actions/on-mouse-down @hover selected edition drawing-tool text-editing? node-editing?
drawing-path? create-comment? space? viewport-ref zoom panning
workspace-read-only?)
drawing-path? create-comment? space? panning workspace-read-only?)
on-mouse-up (actions/on-mouse-up disable-paste)
on-pointer-down (actions/on-pointer-down)
on-pointer-enter (actions/on-pointer-enter in-viewport?)
on-pointer-leave (actions/on-pointer-leave in-viewport?)
on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream)
on-pointer-move (actions/on-pointer-move move-stream)
on-pointer-up (actions/on-pointer-up)
on-move-selected (actions/on-move-selected hover hover-ids selected space? workspace-read-only?)
on-menu-selected (actions/on-menu-selected hover hover-ids selected workspace-read-only?)
@ -269,7 +287,7 @@
:preserveAspectRatio "xMidYMid meet"
:key (str "viewport" page-id)
:view-box (utils/format-viewbox vbox)
:ref viewport-ref
:ref on-viewport-ref
:class (when drawing-tool "drawing")
:style {:cursor @cursor}
:fill "none"
@ -302,7 +320,7 @@
outlined-frame (get objects outlined-frame-id)]
[:*
[:& outline/shape-outlines
{:objects objects-modified
{:objects base-objects
:hover #{outlined-frame-id}
:zoom zoom
:modifiers modifiers}]
@ -423,8 +441,7 @@
[:& scroll-bars/viewport-scrollbars
{:objects base-objects
:zoom zoom
:vbox vbox
:viewport-ref viewport-ref}]
:vbox vbox}]
(when show-rules?
[:& rules/rules
@ -443,25 +460,25 @@
;; DEBUG LAYOUT DROP-ZONES
(when (debug? :layout-drop-zones)
[:& wvd/debug-drop-zones {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :layout-content-bounds)
[:& wvd/debug-content-bounds {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :layout-lines)
[:& wvd/debug-layout-lines {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
(when (debug? :parent-bounds)
[:& wvd/debug-parent-bounds {:selected-shapes selected-shapes
:objects objects-modified
:objects base-objects
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])

View file

@ -19,7 +19,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
[app.util.dom.normalize-wheel :as nw]
@ -34,11 +34,11 @@
(defn on-mouse-down
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? space? viewport-ref zoom panning
node-editing? drawing-path? create-comment? space? panning
workspace-read-only?]
(mf/use-callback
(mf/deps id blocked hidden type selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? @space? viewport-ref zoom
node-editing? drawing-path? create-comment? @space?
panning workspace-read-only?)
(fn [bevent]
(when (or (dom/class? (dom/get-target bevent) "viewport-controls")
@ -61,8 +61,7 @@
(dom/prevent-default bevent)
(if mod?
(let [raw-pt (dom/get-client-position event)
viewport (mf/ref-val viewport-ref)
pt (utils/translate-point-to-viewport viewport zoom raw-pt)]
pt (uwvv/point->viewport raw-pt)]
(st/emit! (dw/start-zooming pt)))
(st/emit! (dw/start-panning))))
@ -322,15 +321,12 @@
(= "TEXTAREA" (obj/get target "tagName")))]
(st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta? editing?))))))
(defn on-mouse-move [viewport-ref zoom]
(defn on-mouse-move []
(let [last-position (mf/use-var nil)]
(mf/use-callback
(mf/deps zoom)
(fn [event]
(let [event (.getBrowserEvent ^js event)
raw-pt (dom/get-client-position event)
viewport (mf/ref-val viewport-ref)
pt (utils/translate-point-to-viewport viewport zoom raw-pt)
(let [raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)
;; We calculate the delta because Safari's MouseEvent.movementX/Y drop
;; events
@ -350,30 +346,27 @@
(kbd/alt? event)
(kbd/meta? event))))))))
(defn on-pointer-move [viewport-ref zoom move-stream]
(defn on-pointer-move [move-stream]
(mf/use-callback
(mf/deps zoom move-stream)
(mf/deps move-stream)
(fn [event]
(let [raw-pt (dom/get-client-position event)
viewport (mf/ref-val viewport-ref)
pt (utils/translate-point-to-viewport viewport zoom raw-pt)]
pt (uwvv/point->viewport raw-pt)]
(rx/push! move-stream pt)))))
(defn on-mouse-wheel [viewport-ref zoom]
(defn on-mouse-wheel [zoom]
(mf/use-callback
(mf/deps zoom)
(fn [event]
(let [viewport (mf/ref-val viewport-ref)
event (.getBrowserEvent ^js event)
(let [event (.getBrowserEvent ^js event)
target (dom/get-target event)
mod? (kbd/mod? event)]
(when (dom/is-child? viewport target)
(when (uwvv/inside-viewport? target)
(dom/prevent-default event)
(dom/stop-propagation event)
(let [pt (->> (dom/get-client-position event)
(utils/translate-point-to-viewport viewport zoom))
(let [raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)
norm-event ^js (nw/normalize-wheel event)
ctrl? (kbd/ctrl? event)
delta-y (.-pixelY norm-event)
@ -413,14 +406,12 @@
(dom/prevent-default e)))))
(defn on-drop
[file viewport-ref zoom]
[file]
(mf/use-fn
(mf/deps zoom)
(fn [event]
(dom/prevent-default event)
(let [point (gpt/point (.-clientX event) (.-clientY event))
viewport (mf/ref-val viewport-ref)
viewport-coord (utils/translate-point-to-viewport viewport zoom point)
viewport-coord (uwvv/point->viewport point)
asset-id (-> (dnd/get-data event "text/asset-id") uuid/uuid)
asset-name (dnd/get-data event "text/asset-name")
asset-type (dnd/get-data event "text/asset-type")]

View file

@ -35,21 +35,29 @@
(defn setup-dom-events [viewport-ref zoom disable-paste in-viewport? workspace-read-only?]
(let [on-key-down (actions/on-key-down)
on-key-up (actions/on-key-up)
on-mouse-move (actions/on-mouse-move viewport-ref zoom)
on-mouse-wheel (actions/on-mouse-wheel viewport-ref zoom)
on-mouse-move (actions/on-mouse-move)
on-mouse-wheel (actions/on-mouse-wheel zoom)
on-paste (actions/on-paste disable-paste in-viewport? workspace-read-only?)]
;; We use the DOM listener because the goog.closure one forces reflow to generate its internal
;; structure. As we don't need currently nothing from BrowserEvent we optimize by using the basic event
(mf/use-layout-effect
(mf/deps on-mouse-move)
(fn []
(let [node (mf/ref-val viewport-ref)]
(.addEventListener node "mousemove" on-mouse-move)
(fn []
(.removeEventListener node "mousemove" on-mouse-move)))))
(mf/use-layout-effect
(mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-paste workspace-read-only?)
(fn []
(let [node (mf/ref-val viewport-ref)
keys [(events/listen js/document EventType.KEYDOWN on-key-down)
(let [keys [(events/listen js/document EventType.KEYDOWN on-key-down)
(events/listen js/document EventType.KEYUP on-key-up)
(events/listen node EventType.MOUSEMOVE on-mouse-move)
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
(events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false})
(events/listen js/window EventType.PASTE on-paste)]]
(fn []
(doseq [key keys]
(events/unlistenByKey key))))))))

View file

@ -11,7 +11,7 @@
[app.common.pages.helpers :as cph]
[app.main.data.workspace :as dw]
[app.main.store :as st]
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
@ -27,7 +27,7 @@
(mf/defc viewport-scrollbars
{::mf/wrap [mf/memo]}
[{:keys [objects viewport-ref zoom vbox]}]
[{:keys [objects zoom vbox]}]
(let [v-scrolling? (mf/use-state false)
h-scrolling? (mf/use-state false)
@ -126,10 +126,9 @@
on-mouse-move
(fn [event axis]
(when-let [_ (or @v-scrolling? @h-scrolling?)]
(let [viewport (mf/ref-val viewport-ref)
start-pt (mf/ref-val start-ref)
(let [start-pt (mf/ref-val start-ref)
current-pt (dom/get-client-position event)
current-pt-viewport (utils/translate-point-to-viewport viewport zoom current-pt)
current-pt-viewport (point->viewport current-pt)
y-delta (/ (* (mf/ref-val height-factor-ref) (- (:y current-pt) (:y start-pt))) zoom)
x-delta (/ (* (mf/ref-val width-factor-ref) (- (:x current-pt) (:x start-pt))) zoom)
new-v-scrollbar-y (-> current-pt-viewport
@ -150,9 +149,8 @@
on-mouse-down
(fn [event axis]
(let [viewport (mf/ref-val viewport-ref)
start-pt (dom/get-client-position event)
viewport-point (utils/translate-point-to-viewport viewport zoom start-pt)
(let [start-pt (dom/get-client-position event)
viewport-point (point->viewport start-pt)
new-h-scrollbar-x (:x viewport-point)
new-v-scrollbar-y (:y viewport-point)
v-scrollbar-y-padding (- v-scrollbar-y new-v-scrollbar-y)

View file

@ -6,13 +6,11 @@
(ns app.main.ui.workspace.viewport.utils
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.main.ui.cursors :as cur]
[app.main.ui.formats :refer [format-number]]
[app.util.dom :as dom]))
[app.main.ui.formats :refer [format-number]]))
(defn format-viewbox [vbox]
(dm/str (format-number(:x vbox 0)) " "
@ -20,17 +18,6 @@
(format-number (:width vbox 0)) " "
(format-number (:height vbox 0))))
(defn translate-point-to-viewport [viewport zoom pt]
(let [vbox (.. ^js viewport -viewBox -baseVal)
brect (dom/get-bounding-rect viewport)
brect (gpt/point (d/parse-integer (:left brect))
(d/parse-integer (:top brect)))
box (gpt/point (.-x vbox) (.-y vbox))
zoom (gpt/point zoom)]
(-> (gpt/subtract pt brect)
(gpt/divide zoom)
(gpt/add box))))
(defn get-cursor [cursor]
(case cursor
:hand cur/hand

View file

@ -0,0 +1,65 @@
;; 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.main.ui.workspace.viewport.viewport-ref
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.main.store :as st]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(defonce viewport-ref (atom nil))
(defonce current-observer (atom nil))
(defonce viewport-brect (atom nil))
(defn init-observer
[node on-change-bounds]
(let [observer (js/ResizeObserver. on-change-bounds)]
(when (some? @current-observer)
(.disconnect @current-observer))
(reset! current-observer observer)
(when (some? node)
(.observe observer node))))
(defn on-change-bounds
[_]
(when @viewport-ref
(let [brect (dom/get-bounding-rect @viewport-ref)
brect (gpt/point (d/parse-integer (:left brect))
(d/parse-integer (:top brect)))]
(reset! viewport-brect brect))))
(defn create-viewport-ref
[]
(let [ref (mf/use-ref nil)]
[ref
(mf/use-memo
#(fn [node]
(mf/set-ref-val! ref node)
(reset! viewport-ref node)
(init-observer node on-change-bounds)))]))
(defn point->viewport
[pt]
(let [zoom (dm/get-in @st/state [:workspace-local :zoom])]
(when (some? @viewport-ref)
(let [vbox (.. ^js @viewport-ref -viewBox -baseVal)
brect @viewport-brect
box (gpt/point (.-x vbox) (.-y vbox))
zoom (gpt/point zoom)]
(-> (gpt/subtract pt brect)
(gpt/divide zoom)
(gpt/add box))))))
(defn inside-viewport?
[target]
(dom/is-child? @viewport-ref target))

View file

@ -890,8 +890,7 @@
(defn calculate-ratio
;; sqrt((actual-width)**2 + (actual-height)**2)/sqrt(2).
[width height]
(/ (mth/sqrt (+ (mth/pow width 2)
(mth/pow height 2)))
(/ (mth/hypot width height)
(mth/sqrt 2)))
(defn fix-percents

View file

@ -63,7 +63,8 @@
[shape-id]
(when (some? shape-id)
(let [text-nodes (dom/query-all (dm/str "#html-text-node-" shape-id " .text-node"))
(let [text-nodes (-> (dom/query (dm/fmt "#html-text-node-%" shape-id))
(dom/query-all ".text-node"))
load-fonts (->> text-nodes (map resolve-font))
process-text-node