mirror of
https://github.com/penpot/penpot.git
synced 2025-05-21 01:06:11 +02:00
⚡ Improve performance on selection react component
Mainly do more static calls and reduce unnecesary allocation
This commit is contained in:
parent
0ea07469d2
commit
1465ed3607
2 changed files with 338 additions and 262 deletions
|
@ -8,6 +8,7 @@
|
||||||
"Selection handlers component."
|
"Selection handlers component."
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
@ -19,11 +20,11 @@
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.css-cursors :as cur]
|
[app.main.ui.css-cursors :as cur]
|
||||||
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
|
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
|
||||||
|
[app.util.array :as array]
|
||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.v2 :as mf]
|
[rumext.v2 :as mf]))
|
||||||
[rumext.v2.util :refer [map->obj]]))
|
|
||||||
|
|
||||||
(def rotation-handler-size 20)
|
(def rotation-handler-size 20)
|
||||||
(def resize-point-radius 4)
|
(def resize-point-radius 4)
|
||||||
|
@ -37,134 +38,150 @@
|
||||||
(def small-selrect-side 30)
|
(def small-selrect-side 30)
|
||||||
|
|
||||||
(mf/defc selection-rect
|
(mf/defc selection-rect
|
||||||
|
{::mf/wrap-props false}
|
||||||
[{:keys [transform rect zoom color on-move-selected on-context-menu]}]
|
[{:keys [transform rect zoom color on-move-selected on-context-menu]}]
|
||||||
(when rect
|
(let [x (dm/get-prop rect :x)
|
||||||
(let [{:keys [x y width height]} rect]
|
y (dm/get-prop rect :y)
|
||||||
[:rect.main.viewport-selrect
|
width (dm/get-prop rect :width)
|
||||||
{:x x
|
height (dm/get-prop rect :height)]
|
||||||
:y y
|
[:rect.main.viewport-selrect
|
||||||
:width width
|
{:x x
|
||||||
:height height
|
:y y
|
||||||
:transform (str transform)
|
:width width
|
||||||
:on-pointer-down on-move-selected
|
:height height
|
||||||
:on-context-menu on-context-menu
|
:transform (str transform)
|
||||||
:style {:stroke color
|
:on-pointer-down on-move-selected
|
||||||
:stroke-width (/ selection-rect-width zoom)
|
:on-context-menu on-context-menu
|
||||||
:fill "none"}}])))
|
:style {:stroke color
|
||||||
|
:stroke-width (/ selection-rect-width zoom)
|
||||||
|
:fill "none"}}]))
|
||||||
|
|
||||||
(defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom]
|
(defn- calculate-handlers
|
||||||
(let [threshold-small (/ 25 zoom)
|
"Calculates selection handlers for the current selection."
|
||||||
threshold-tiny (/ 10 zoom)
|
[selection shape zoom]
|
||||||
|
(let [x (dm/get-prop selection :x)
|
||||||
|
y (dm/get-prop selection :y)
|
||||||
|
width (dm/get-prop selection :width)
|
||||||
|
height (dm/get-prop selection :height)
|
||||||
|
|
||||||
small-width? (<= width threshold-small)
|
threshold-small (/ 25 zoom)
|
||||||
tiny-width? (<= width threshold-tiny)
|
threshold-tiny (/ 10 zoom)
|
||||||
|
|
||||||
small-height? (<= height threshold-small)
|
small-width? (<= width threshold-small)
|
||||||
tiny-height? (<= height threshold-tiny)
|
tiny-width? (<= width threshold-tiny)
|
||||||
|
|
||||||
vertical-line? (and (= type :path) tiny-width?)
|
small-height? (<= height threshold-small)
|
||||||
horizontal-line? (and (= type :path) tiny-height?)
|
tiny-height? (<= height threshold-tiny)
|
||||||
|
|
||||||
align (if (or small-width? small-height?)
|
path? (cfh/path-shape? shape)
|
||||||
:outside
|
vertical-line? (and ^boolean path? ^boolean tiny-width?)
|
||||||
:inside)]
|
horizontal-line? (and ^boolean path? ^boolean tiny-height?)
|
||||||
(->>
|
|
||||||
[;; TOP-LEFT
|
|
||||||
{:type :rotation
|
|
||||||
:position :top-left
|
|
||||||
:props {:cx x :cy y}}
|
|
||||||
|
|
||||||
{:type :rotation
|
align (if (or ^boolean small-width? ^boolean small-height?)
|
||||||
:position :top-right
|
:outside
|
||||||
:props {:cx (+ x width) :cy y}}
|
:inside)
|
||||||
|
|
||||||
{:type :rotation
|
result #js [#js {:type :rotation
|
||||||
:position :bottom-right
|
:position :top-left
|
||||||
:props {:cx (+ x width) :cy (+ y height)}}
|
:props #js {:cx x :cy y}}
|
||||||
|
|
||||||
{:type :rotation
|
#js {:type :rotation
|
||||||
:position :bottom-left
|
:position :top-right
|
||||||
:props {:cx x :cy (+ y height)}}
|
:props #js {:cx (+ x width) :cy y}}
|
||||||
|
|
||||||
(when-not horizontal-line?
|
#js {:type :rotation
|
||||||
(let [x (if small-width? (+ x (/ (- width threshold-small) 2)) x)
|
:position :bottom-right
|
||||||
length (if small-width? threshold-small width)]
|
:props #js {:cx (+ x width) :cy (+ y height)}}
|
||||||
{:type :resize-side
|
|
||||||
:position :top
|
|
||||||
:props {:x x
|
|
||||||
:y y
|
|
||||||
:length length
|
|
||||||
:angle 0
|
|
||||||
:align align
|
|
||||||
:show-handler? tiny-width?}}))
|
|
||||||
|
|
||||||
(when-not horizontal-line?
|
#js {:type :rotation
|
||||||
(let [x (if small-width? (+ x (/ (+ width threshold-small) 2)) (+ x width))
|
:position :bottom-left
|
||||||
length (if small-width? threshold-small width)]
|
:props #js {:cx x :cy (+ y height)}}]]
|
||||||
{:type :resize-side
|
|
||||||
:position :bottom
|
|
||||||
:props {:x x
|
|
||||||
:y (+ y height)
|
|
||||||
:length length
|
|
||||||
:angle 180
|
|
||||||
:align align
|
|
||||||
:show-handler? tiny-width?}}))
|
|
||||||
|
|
||||||
(when-not vertical-line?
|
|
||||||
(let [y (if small-height? (+ y (/ (- height threshold-small) 2)) y)
|
|
||||||
length (if small-height? threshold-small height)]
|
|
||||||
{:type :resize-side
|
|
||||||
:position :right
|
|
||||||
:props {:x (+ x width)
|
|
||||||
:y y
|
|
||||||
:length length
|
|
||||||
:angle 90
|
|
||||||
:align align
|
|
||||||
:show-handler? tiny-height?}}))
|
|
||||||
|
|
||||||
(when-not vertical-line?
|
(when-not ^boolean horizontal-line?
|
||||||
(let [y (if small-height? (+ y (/ (+ height threshold-small) 2)) (+ y height))
|
(array/conj! result
|
||||||
length (if small-height? threshold-small height)]
|
#js {:type :resize-side
|
||||||
{:type :resize-side
|
:position :top
|
||||||
:position :left
|
:props #js {:x (if ^boolean small-width?
|
||||||
:props {:x x
|
(+ x (/ (- width threshold-small) 2))
|
||||||
:y y
|
x)
|
||||||
:length length
|
:y y
|
||||||
:angle 270
|
:length (if ^boolean small-width?
|
||||||
:align align
|
threshold-small
|
||||||
:show-handler? tiny-height?}}))
|
width)
|
||||||
|
:angle 0
|
||||||
|
:align align
|
||||||
|
:show-handler tiny-width?}}
|
||||||
|
#js {:type :resize-side
|
||||||
|
:position :bottom
|
||||||
|
:props #js {:x (if ^boolean small-width?
|
||||||
|
(+ x (/ (+ width threshold-small) 2))
|
||||||
|
(+ x width))
|
||||||
|
:y (+ y height)
|
||||||
|
:length (if small-width? threshold-small width)
|
||||||
|
:angle 180
|
||||||
|
:align align
|
||||||
|
:show-handler tiny-width?}}))
|
||||||
|
|
||||||
(when (and (not tiny-width?) (not tiny-height?))
|
(when-not vertical-line?
|
||||||
{:type :resize-point
|
(array/conj! result
|
||||||
:position :top-left
|
#js {:type :resize-side
|
||||||
:props {:cx x :cy y :align align}})
|
:position :right
|
||||||
|
:props #js {:x (+ x width)
|
||||||
|
:y (if small-height? (+ y (/ (- height threshold-small) 2)) y)
|
||||||
|
:length (if small-height? threshold-small height)
|
||||||
|
:angle 90
|
||||||
|
:align align
|
||||||
|
:show-handler tiny-height?}}
|
||||||
|
|
||||||
(when (and (not tiny-width?) (not tiny-height?))
|
#js {:type :resize-side
|
||||||
{:type :resize-point
|
:position :left
|
||||||
:position :top-right
|
:props #js {:x x
|
||||||
:props {:cx (+ x width) :cy y :align align}})
|
:y (if ^boolean small-height?
|
||||||
|
(+ y (/ (+ height threshold-small) 2))
|
||||||
|
(+ y height))
|
||||||
|
:length (if ^boolean small-height?
|
||||||
|
threshold-small
|
||||||
|
height)
|
||||||
|
:angle 270
|
||||||
|
:align align
|
||||||
|
:show-handler tiny-height?}}))
|
||||||
|
|
||||||
(when (and (not tiny-width?) (not tiny-height?))
|
(when (and (not tiny-width?) (not tiny-height?))
|
||||||
{:type :resize-point
|
(array/conj! result
|
||||||
:position :bottom-right
|
#js {:type :resize-point
|
||||||
:props {:cx (+ x width) :cy (+ y height) :align align}})
|
:position :top-left
|
||||||
|
:props #js {:cx x :cy y :align align}}
|
||||||
|
#js {:type :resize-point
|
||||||
|
:position :top-right
|
||||||
|
:props #js {:cx (+ x width) :cy y :align align}}
|
||||||
|
#js {:type :resize-point
|
||||||
|
:position :bottom-right
|
||||||
|
:props #js {:cx (+ x width) :cy (+ y height) :align align}}
|
||||||
|
#js {:type :resize-point
|
||||||
|
:position :bottom-left
|
||||||
|
:props #js {:cx x :cy (+ y height) :align align}}))))
|
||||||
|
|
||||||
(when (and (not tiny-width?) (not tiny-height?))
|
(mf/defc rotation-handler
|
||||||
{:type :resize-point
|
{::mf/wrap-props false}
|
||||||
:position :bottom-left
|
[{:keys [cx cy transform position rotation zoom on-rotate] :as props}]
|
||||||
:props {:cx x :cy (+ y height) :align align}})]
|
(let [size (/ rotation-handler-size zoom)
|
||||||
|
delta-x (if (or (= position :top-left)
|
||||||
|
(= position :bottom-left))
|
||||||
|
size
|
||||||
|
0)
|
||||||
|
delta-y (if (or (= :top-left position)
|
||||||
|
(= :top-right position))
|
||||||
|
size
|
||||||
|
0)
|
||||||
|
|
||||||
(filterv (comp not nil?)))))
|
x (- cx delta-x)
|
||||||
|
y (- cy delta-y)
|
||||||
(mf/defc rotation-handler [{:keys [cx cy transform position rotation zoom on-rotate]}]
|
angle (case position
|
||||||
(let [size (/ rotation-handler-size zoom)
|
:top-left 0
|
||||||
x (- cx (if (#{:top-left :bottom-left} position) size 0))
|
:top-right 90
|
||||||
y (- cy (if (#{:top-left :top-right} position) size 0))
|
:bottom-right 180
|
||||||
angle (case position
|
:bottom-left 270)]
|
||||||
:top-left 0
|
|
||||||
:top-right 90
|
|
||||||
:bottom-right 180
|
|
||||||
:bottom-left 270)]
|
|
||||||
[:rect {:x x
|
[:rect {:x x
|
||||||
:y y
|
:y y
|
||||||
:class (cur/get-dynamic "rotate" (+ rotation angle))
|
:class (cur/get-dynamic "rotate" (+ rotation angle))
|
||||||
|
@ -176,13 +193,20 @@
|
||||||
:on-pointer-down on-rotate}]))
|
:on-pointer-down on-rotate}]))
|
||||||
|
|
||||||
(mf/defc resize-point-handler
|
(mf/defc resize-point-handler
|
||||||
[{:keys [cx cy zoom position on-resize transform rotation color align]}]
|
{::mf/wrap-props false}
|
||||||
(let [layout (mf/deref refs/workspace-layout)
|
[{:keys [cx cy zoom position on-resize transform rotation color align scale-text]}]
|
||||||
scale-text (:scale-text layout)
|
(let [cursor (if (or (= position :top-left)
|
||||||
cursor (if (#{:top-left :bottom-right} position)
|
(= position :bottom-right))
|
||||||
(if scale-text (cur/get-dynamic "scale-nesw" rotation) (cur/get-dynamic "resize-nesw" rotation))
|
(if ^boolean scale-text
|
||||||
(if scale-text (cur/get-dynamic "scale-nwse" rotation) (cur/get-dynamic "resize-nwse" rotation)))
|
(cur/get-dynamic "scale-nesw" rotation)
|
||||||
{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)]
|
(cur/get-dynamic "resize-nesw" rotation))
|
||||||
|
(if ^boolean scale-text
|
||||||
|
(cur/get-dynamic "scale-nwse" rotation)
|
||||||
|
(cur/get-dynamic "resize-nwse" rotation)))
|
||||||
|
|
||||||
|
pt (gpt/transform (gpt/point cx cy) transform)
|
||||||
|
cx' (dm/get-prop pt :x)
|
||||||
|
cy' (dm/get-prop pt :y)]
|
||||||
|
|
||||||
[:g.resize-handler
|
[:g.resize-handler
|
||||||
[:circle {:r (/ resize-point-radius zoom)
|
[:circle {:r (/ resize-point-radius zoom)
|
||||||
|
@ -196,49 +220,68 @@
|
||||||
|
|
||||||
(if (= align :outside)
|
(if (= align :outside)
|
||||||
(let [resize-point-circle-radius (/ resize-point-circle-radius zoom)
|
(let [resize-point-circle-radius (/ resize-point-circle-radius zoom)
|
||||||
offset-x (if (#{:top-right :bottom-right} position) 0 (- resize-point-circle-radius))
|
offset-x (if (or (= position :top-right)
|
||||||
offset-y (if (#{:bottom-left :bottom-right} position) 0 (- resize-point-circle-radius))
|
(= position :bottom-right))
|
||||||
cx (+ cx offset-x)
|
0
|
||||||
cy (+ cy offset-y)
|
(- resize-point-circle-radius))
|
||||||
{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform)]
|
offset-y (if (or (= position :bottom-left)
|
||||||
|
(= position :bottom-right))
|
||||||
|
0
|
||||||
|
(- resize-point-circle-radius))
|
||||||
|
cx (+ cx offset-x)
|
||||||
|
cy (+ cy offset-y)
|
||||||
|
pt (gpt/transform (gpt/point cx cy) transform)
|
||||||
|
cx' (dm/get-prop pt :x)
|
||||||
|
cy' (dm/get-prop pt :y)]
|
||||||
[:rect {:x cx'
|
[:rect {:x cx'
|
||||||
:y cy'
|
:y cy'
|
||||||
|
:data-position (name position)
|
||||||
:class cursor
|
:class cursor
|
||||||
:width resize-point-circle-radius
|
|
||||||
:height resize-point-circle-radius
|
|
||||||
:transform (when rotation (dm/fmt "rotate(%, %, %)" rotation cx' cy'))
|
|
||||||
:style {:fill (if (dbg/enabled? :handlers) "red" "none")
|
:style {:fill (if (dbg/enabled? :handlers) "red" "none")
|
||||||
:stroke-width 0}
|
:stroke-width 0}
|
||||||
:on-pointer-down #(on-resize {:x cx' :y cy'} %)}])
|
:width resize-point-circle-radius
|
||||||
|
:height resize-point-circle-radius
|
||||||
|
:transform (when (some? rotation)
|
||||||
|
(dm/fmt "rotate(%, %, %)" rotation cx' cy'))
|
||||||
|
:on-pointer-down on-resize}])
|
||||||
|
|
||||||
[:circle {:on-pointer-down #(on-resize {:x cx' :y cy'} %)
|
[:circle {:on-pointer-down on-resize
|
||||||
:r (/ resize-point-circle-radius zoom)
|
:r (/ resize-point-circle-radius zoom)
|
||||||
|
:data-position (name position)
|
||||||
:cx cx'
|
:cx cx'
|
||||||
:cy cy'
|
:cy cy'
|
||||||
|
:data-x cx'
|
||||||
|
:data-y cy'
|
||||||
:class cursor
|
:class cursor
|
||||||
:style {:fill (if (dbg/enabled? :handlers) "red" "none")
|
:style {:fill (if (dbg/enabled? :handlers) "red" "none")
|
||||||
:stroke-width 0}}])]))
|
:stroke-width 0}}])]))
|
||||||
|
|
||||||
|
;; The side handler is always rendered horizontally and then rotated
|
||||||
(mf/defc resize-side-handler
|
(mf/defc resize-side-handler
|
||||||
"The side handler is always rendered horizontally and then rotated"
|
{::mf/wrap-props false}
|
||||||
[{:keys [x y length align angle zoom position rotation transform on-resize color show-handler?]}]
|
[{:keys [x y length align angle zoom position rotation transform on-resize color show-handler scale-text]}]
|
||||||
(let [res-point (if (#{:top :bottom} position)
|
(let [height (/ resize-side-height zoom)
|
||||||
{:y y}
|
offset-y (if (= align :outside) (- height) (- (/ height 2)))
|
||||||
{:x x})
|
target-y (+ y offset-y)
|
||||||
layout (mf/deref refs/workspace-layout)
|
transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))
|
||||||
scale-text (:scale-text layout)
|
cursor (if (or (= position :left)
|
||||||
height (/ resize-side-height zoom)
|
(= position :right))
|
||||||
offset-y (if (= align :outside) (- height) (- (/ height 2)))
|
(if ^boolean scale-text
|
||||||
target-y (+ y offset-y)
|
(cur/get-dynamic "scale-ew" rotation)
|
||||||
transform-str (dm/str (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))))]
|
(cur/get-dynamic "resize-ew" rotation))
|
||||||
|
(if ^boolean scale-text
|
||||||
|
(cur/get-dynamic "scale-ns" rotation)
|
||||||
|
(cur/get-dynamic "resize-ns" rotation)))]
|
||||||
|
|
||||||
[:g.resize-handler
|
[:g.resize-handler
|
||||||
(when show-handler?
|
(when ^boolean show-handler
|
||||||
[:circle {:r (/ resize-point-radius zoom)
|
[:circle {:r (/ resize-point-radius zoom)
|
||||||
:style {:fillOpacity 1
|
:style {:fillOpacity 1
|
||||||
:stroke color
|
:stroke color
|
||||||
:strokeWidth "1px"
|
:strokeWidth "1px"
|
||||||
:fill "var(--color-white)"
|
:fill "var(--color-white)"
|
||||||
:vectorEffect "non-scaling-stroke"}
|
:vectorEffect "non-scaling-stroke"}
|
||||||
|
:data-position (name position)
|
||||||
:cx (+ x (/ length 2))
|
:cx (+ x (/ length 2))
|
||||||
:cy y
|
:cy y
|
||||||
:transform transform-str}])
|
:transform transform-str}])
|
||||||
|
@ -246,42 +289,25 @@
|
||||||
:y target-y
|
:y target-y
|
||||||
:width length
|
:width length
|
||||||
:height height
|
:height height
|
||||||
:class (if (#{:left :right} position)
|
:class cursor
|
||||||
(if scale-text (cur/get-dynamic "scale-ew" rotation) (cur/get-dynamic "resize-ew" rotation))
|
:data-position (name position)
|
||||||
(if scale-text (cur/get-dynamic "scale-ns" rotation) (cur/get-dynamic "resize-ns" rotation)))
|
|
||||||
:transform transform-str
|
:transform transform-str
|
||||||
:on-pointer-down #(on-resize res-point %)
|
:on-pointer-down on-resize
|
||||||
:style {:fill (if (dbg/enabled? :handlers) "yellow" "none")
|
:style {:fill (if (dbg/enabled? :handlers) "yellow" "none")
|
||||||
:stroke-width 0}}]]))
|
:stroke-width 0}}]]))
|
||||||
|
|
||||||
(defn minimum-selrect [{:keys [x y width height] :as selrect}]
|
|
||||||
(let [final-width (max width min-selrect-side)
|
|
||||||
final-height (max height min-selrect-side)
|
|
||||||
offset-x (/ (- final-width width) 2)
|
|
||||||
offset-y (/ (- final-height height) 2)]
|
|
||||||
{:x (- x offset-x)
|
|
||||||
:y (- y offset-y)
|
|
||||||
:width final-width
|
|
||||||
:height final-height}))
|
|
||||||
|
|
||||||
(mf/defc controls-selection
|
(mf/defc controls-selection
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[{:keys [shape zoom color on-move-selected on-context-menu disable-handlers]}]
|
||||||
(let [shape (obj/get props "shape")
|
(let [selrect (dm/get-prop shape :selrect)
|
||||||
zoom (obj/get props "zoom")
|
transform-type (mf/deref refs/current-transform)
|
||||||
color (obj/get props "color")
|
transform (gsh/transform-str shape)]
|
||||||
on-move-selected (obj/get props "on-move-selected")
|
|
||||||
on-context-menu (obj/get props "on-context-menu")
|
|
||||||
disable-handlers (obj/get props "disable-handlers")
|
|
||||||
|
|
||||||
current-transform (mf/deref refs/current-transform)
|
(when (and (some? selrect)
|
||||||
|
(not (:transforming shape))
|
||||||
selrect (:selrect shape)
|
(not (or (= transform-type :move)
|
||||||
transform (gsh/transform-str shape)]
|
(= transform-type :rotate))))
|
||||||
|
[:g.controls {:pointer-events (if ^boolean disable-handlers "none" "visible")}
|
||||||
(when (and (not (:transforming shape))
|
|
||||||
(not (#{:move :rotate} current-transform)))
|
|
||||||
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
|
|
||||||
;; Selection rect
|
;; Selection rect
|
||||||
[:& selection-rect {:rect selrect
|
[:& selection-rect {:rect selrect
|
||||||
:transform transform
|
:transform transform
|
||||||
|
@ -292,54 +318,61 @@
|
||||||
|
|
||||||
(mf/defc controls-handlers
|
(mf/defc controls-handlers
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[{:keys [shape zoom color on-resize on-rotate disable-handlers]}]
|
||||||
(let [shape (obj/get props "shape")
|
(let [transform-type (mf/deref refs/current-transform)
|
||||||
zoom (obj/get props "zoom")
|
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||||
color (obj/get props "color")
|
|
||||||
on-resize (obj/get props "on-resize")
|
|
||||||
on-rotate (obj/get props "on-rotate")
|
|
||||||
disable-handlers (obj/get props "disable-handlers")
|
|
||||||
current-transform (mf/deref refs/current-transform)
|
|
||||||
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
|
|
||||||
|
|
||||||
selrect (:selrect shape)
|
layout (mf/deref refs/workspace-layout)
|
||||||
transform (gsh/transform-matrix shape)
|
scale-text? (contains? layout :scale-text)
|
||||||
|
|
||||||
rotation (-> (gpt/point 1 0)
|
selrect (dm/get-prop shape :selrect)
|
||||||
(gpt/transform (:transform shape))
|
transform (gsh/transform-matrix shape)
|
||||||
(gpt/angle)
|
|
||||||
(mod 360))]
|
|
||||||
|
|
||||||
(when (and (not (#{:move :rotate} current-transform))
|
rotation (-> (gpt/point 1 0)
|
||||||
(not workspace-read-only?)
|
(gpt/transform (:transform shape))
|
||||||
(not (:transforming shape)))
|
(gpt/angle)
|
||||||
[:g.controls {:pointer-events (if disable-handlers "none" "visible")}
|
(mod 360))
|
||||||
;; Handlers
|
|
||||||
(for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)]
|
|
||||||
(let [rotation
|
|
||||||
(cond
|
|
||||||
(and (#{:top-left :bottom-right} position)
|
|
||||||
(or (and (:flip-x shape) (not (:flip-y shape)))
|
|
||||||
(and (:flip-y shape) (not (:flip-x shape)))))
|
|
||||||
(- rotation 90)
|
|
||||||
|
|
||||||
(and (#{:top-right :bottom-left} position)
|
flip-x (get shape :flip-x)
|
||||||
(or (and (:flip-x shape) (not (:flip-y shape)))
|
flip-y (get shape :flip-y)
|
||||||
(and (:flip-y shape) (not (:flip-x shape)))))
|
half-flip? (or (and (some? flip-x) (not (some? flip-y)))
|
||||||
(+ rotation 90)
|
(and (some? flip-y) (not (some? flip-x))))]
|
||||||
|
|
||||||
:else
|
(when (and (not ^boolean read-only?)
|
||||||
rotation)
|
(not (:transforming shape))
|
||||||
|
(not (or (= transform-type :move)
|
||||||
|
(= transform-type :rotate))))
|
||||||
|
|
||||||
common-props {:key (dm/str (name type) "-" (name position))
|
[:g.controls {:pointer-events (if ^boolean disable-handlers "none" "visible")}
|
||||||
:zoom zoom
|
(for [handler (calculate-handlers selrect shape zoom)]
|
||||||
:position position
|
(let [type (obj/get handler "type")
|
||||||
:on-rotate on-rotate
|
position (obj/get handler "position")
|
||||||
:on-resize (partial on-resize position)
|
props (obj/get handler "props")
|
||||||
:transform transform
|
rotation (cond
|
||||||
:rotation rotation
|
(and ^boolean half-flip?
|
||||||
:color color}
|
(or (= position :top-left)
|
||||||
props (map->obj (merge common-props props))]
|
(= position :bottom-right)))
|
||||||
|
(- rotation 90)
|
||||||
|
|
||||||
|
(and ^boolean half-flip?
|
||||||
|
(or (= position :top-right)
|
||||||
|
(= position :bottom-left)))
|
||||||
|
(- rotation 90)
|
||||||
|
|
||||||
|
:else
|
||||||
|
rotation)
|
||||||
|
|
||||||
|
props (obj/merge!
|
||||||
|
#js {:key (dm/str (name type) "-" (name position))
|
||||||
|
:scale-text scale-text?
|
||||||
|
:zoom zoom
|
||||||
|
:position position
|
||||||
|
:on-rotate on-rotate
|
||||||
|
:on-resize on-resize
|
||||||
|
:transform transform
|
||||||
|
:rotation rotation
|
||||||
|
:color color}
|
||||||
|
props)]
|
||||||
(case type
|
(case type
|
||||||
:rotation [:> rotation-handler props]
|
:rotation [:> rotation-handler props]
|
||||||
:resize-point [:> resize-point-handler props]
|
:resize-point [:> resize-point-handler props]
|
||||||
|
@ -348,8 +381,12 @@
|
||||||
;; --- Selection Handlers (Component)
|
;; --- Selection Handlers (Component)
|
||||||
|
|
||||||
(mf/defc text-edition-selection
|
(mf/defc text-edition-selection
|
||||||
[{:keys [shape color zoom] :as props}]
|
{::mf/wrap-props false}
|
||||||
(let [{:keys [x y width height]} shape]
|
[{:keys [shape color zoom]}]
|
||||||
|
(let [x (dm/get-prop shape :x)
|
||||||
|
y (dm/get-prop shape :y)
|
||||||
|
width (dm/get-prop shape :width)
|
||||||
|
height (dm/get-prop shape :height)]
|
||||||
[:g.controls
|
[:g.controls
|
||||||
[:rect.main {:x x :y y
|
[:rect.main {:x x :y y
|
||||||
:transform (gsh/transform-str shape)
|
:transform (gsh/transform-str shape)
|
||||||
|
@ -362,23 +399,31 @@
|
||||||
:fill "none"}}]]))
|
:fill "none"}}]]))
|
||||||
|
|
||||||
(mf/defc multiple-handlers
|
(mf/defc multiple-handlers
|
||||||
[{:keys [shapes selected zoom color disable-handlers] :as props}]
|
{::mf/wrap-props false}
|
||||||
|
[{:keys [shapes selected zoom color disable-handlers]}]
|
||||||
(let [shape (mf/with-memo [shapes]
|
(let [shape (mf/with-memo [shapes]
|
||||||
(-> shapes
|
(-> shapes
|
||||||
(gsh/shapes->rect)
|
(gsh/shapes->rect)
|
||||||
(assoc :type :multiple)
|
(assoc :type :multiple)
|
||||||
(cts/setup-shape)))
|
(cts/setup-shape)))
|
||||||
|
|
||||||
on-resize
|
on-resize
|
||||||
(fn [current-position _initial-position event]
|
(mf/use-fn
|
||||||
(when (dom/left-mouse? event)
|
(mf/deps selected shape)
|
||||||
(dom/stop-propagation event)
|
(fn [event]
|
||||||
(st/emit! (dw/start-resize current-position selected shape))))
|
(when (dom/left-mouse? event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [target (dom/get-current-target event)
|
||||||
|
position (keyword (dom/get-data target "position"))]
|
||||||
|
(st/emit! (dw/start-resize position selected shape))))))
|
||||||
|
|
||||||
on-rotate
|
on-rotate
|
||||||
(fn [event]
|
(mf/use-fn
|
||||||
(when (dom/left-mouse? event)
|
(mf/deps shapes)
|
||||||
(dom/stop-propagation event)
|
(fn [event]
|
||||||
(st/emit! (dw/start-rotate shapes))))]
|
(when (dom/left-mouse? event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(st/emit! (dw/start-rotate shapes)))))]
|
||||||
|
|
||||||
[:& controls-handlers
|
[:& controls-handlers
|
||||||
{:shape shape
|
{:shape shape
|
||||||
|
@ -389,7 +434,8 @@
|
||||||
:on-rotate on-rotate}]))
|
:on-rotate on-rotate}]))
|
||||||
|
|
||||||
(mf/defc multiple-selection
|
(mf/defc multiple-selection
|
||||||
[{:keys [shapes zoom color disable-handlers on-move-selected on-context-menu] :as props}]
|
{::mf/wrap-props false}
|
||||||
|
[{:keys [shapes zoom color disable-handlers on-move-selected on-context-menu]}]
|
||||||
(let [shape (mf/with-memo [shapes]
|
(let [shape (mf/with-memo [shapes]
|
||||||
(-> shapes
|
(-> shapes
|
||||||
(gsh/shapes->rect)
|
(gsh/shapes->rect)
|
||||||
|
@ -405,20 +451,27 @@
|
||||||
:on-context-menu on-context-menu}]))
|
:on-context-menu on-context-menu}]))
|
||||||
|
|
||||||
(mf/defc single-handlers
|
(mf/defc single-handlers
|
||||||
[{:keys [shape zoom color disable-handlers] :as props}]
|
{::mf/wrap-props false}
|
||||||
(let [shape-id (:id shape)
|
[{:keys [shape zoom color disable-handlers]}]
|
||||||
|
(let [shape-id (dm/get-prop shape :id)
|
||||||
|
|
||||||
on-resize
|
on-resize
|
||||||
(fn [current-position _initial-position event]
|
(mf/use-fn
|
||||||
(when (dom/left-mouse? event)
|
(mf/deps shape-id shape)
|
||||||
(dom/stop-propagation event)
|
(fn [event]
|
||||||
(st/emit! (dw/start-resize current-position #{shape-id} shape))))
|
(when (dom/left-mouse? event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [target (dom/get-current-target event)
|
||||||
|
position (keyword (dom/get-data target "position"))]
|
||||||
|
(st/emit! (dw/start-resize position #{shape-id} shape))))))
|
||||||
|
|
||||||
on-rotate
|
on-rotate
|
||||||
(fn [event]
|
(mf/use-fn
|
||||||
(when (dom/left-mouse? event)
|
(mf/deps shape)
|
||||||
(dom/stop-propagation event)
|
(fn [event]
|
||||||
(st/emit! (dw/start-rotate [shape]))))]
|
(when (dom/left-mouse? event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(st/emit! (dw/start-rotate [shape])))))]
|
||||||
|
|
||||||
[:& controls-handlers
|
[:& controls-handlers
|
||||||
{:shape shape
|
{:shape shape
|
||||||
|
@ -429,7 +482,8 @@
|
||||||
:on-resize on-resize}]))
|
:on-resize on-resize}]))
|
||||||
|
|
||||||
(mf/defc single-selection
|
(mf/defc single-selection
|
||||||
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}]
|
{::mf/wrap-props false}
|
||||||
|
[{:keys [shape zoom color disable-handlers on-move-selected on-context-menu]}]
|
||||||
[:& controls-selection
|
[:& controls-selection
|
||||||
{:shape shape
|
{:shape shape
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
|
@ -439,23 +493,25 @@
|
||||||
:on-context-menu on-context-menu}])
|
:on-context-menu on-context-menu}])
|
||||||
|
|
||||||
(mf/defc selection-area
|
(mf/defc selection-area
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap-props false}
|
||||||
[{:keys [shapes edition zoom disable-handlers on-move-selected on-context-menu] :as props}]
|
[{:keys [shapes edition zoom disable-handlers on-move-selected on-context-menu]}]
|
||||||
(let [num (count shapes)
|
(let [total (count shapes)
|
||||||
{:keys [type] :as shape} (first shapes)
|
|
||||||
|
shape (first shapes)
|
||||||
|
shape-id (dm/get-prop shape :id)
|
||||||
|
|
||||||
;; Note that we don't use mf/deref to avoid a repaint dependency here
|
;; Note that we don't use mf/deref to avoid a repaint dependency here
|
||||||
objects (deref refs/workspace-page-objects)
|
objects (deref refs/workspace-page-objects)
|
||||||
|
|
||||||
color (if (and (= num 1)
|
color (if (and (= total 1) ^boolean (ctn/in-any-component? objects shape))
|
||||||
(ctn/in-any-component? objects shape))
|
selection-rect-color-component
|
||||||
selection-rect-color-component
|
selection-rect-color-normal)]
|
||||||
selection-rect-color-normal)]
|
|
||||||
(cond
|
(cond
|
||||||
(zero? num)
|
(zero? total)
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(> num 1)
|
(> total 1)
|
||||||
[:& multiple-selection
|
[:& multiple-selection
|
||||||
{:shapes shapes
|
{:shapes shapes
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
|
@ -464,13 +520,14 @@
|
||||||
:on-move-selected on-move-selected
|
:on-move-selected on-move-selected
|
||||||
:on-context-menu on-context-menu}]
|
:on-context-menu on-context-menu}]
|
||||||
|
|
||||||
(and (= type :text) (= edition (:id shape)))
|
(and (cfh/text-shape? shape)
|
||||||
|
(= edition shape-id))
|
||||||
[:& text-edition-selection
|
[:& text-edition-selection
|
||||||
{:shape shape
|
{:shape shape
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:color color}]
|
:color color}]
|
||||||
|
|
||||||
(= edition (:id shape))
|
(= edition shape-id)
|
||||||
nil
|
nil
|
||||||
|
|
||||||
:else
|
:else
|
||||||
|
@ -483,23 +540,25 @@
|
||||||
:on-context-menu on-context-menu}])))
|
:on-context-menu on-context-menu}])))
|
||||||
|
|
||||||
(mf/defc selection-handlers
|
(mf/defc selection-handlers
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap-props false}
|
||||||
[{:keys [shapes selected edition zoom disable-handlers] :as props}]
|
[{:keys [shapes selected edition zoom disable-handlers]}]
|
||||||
(let [num (count shapes)
|
(let [total (count shapes)
|
||||||
{:keys [type] :as shape} (first shapes)
|
|
||||||
|
shape (first shapes)
|
||||||
|
shape-id (dm/get-prop shape :id)
|
||||||
|
|
||||||
;; Note that we don't use mf/deref to avoid a repaint dependency here
|
;; Note that we don't use mf/deref to avoid a repaint dependency here
|
||||||
objects (deref refs/workspace-page-objects)
|
objects (deref refs/workspace-page-objects)
|
||||||
|
|
||||||
color (if (and (= num 1)
|
color (if (and (= total 1) ^boolean (ctn/in-any-component? objects shape))
|
||||||
(ctn/in-any-component? objects shape))
|
selection-rect-color-component
|
||||||
selection-rect-color-component
|
selection-rect-color-normal)]
|
||||||
selection-rect-color-normal)]
|
|
||||||
(cond
|
(cond
|
||||||
(zero? num)
|
(zero? total)
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(> num 1)
|
(> total 1)
|
||||||
[:& multiple-handlers
|
[:& multiple-handlers
|
||||||
{:shapes shapes
|
{:shapes shapes
|
||||||
:selected selected
|
:selected selected
|
||||||
|
@ -507,10 +566,11 @@
|
||||||
:color color
|
:color color
|
||||||
:disable-handlers disable-handlers}]
|
:disable-handlers disable-handlers}]
|
||||||
|
|
||||||
(and (= type :text) (= edition (:id shape)))
|
(and (cfh/text-shape? shape)
|
||||||
|
(= edition shape-id))
|
||||||
nil
|
nil
|
||||||
|
|
||||||
(= edition (:id shape))
|
(= edition shape-id)
|
||||||
[:& path-editor
|
[:& path-editor
|
||||||
{:zoom zoom
|
{:zoom zoom
|
||||||
:shape shape}]
|
:shape shape}]
|
||||||
|
|
|
@ -15,6 +15,22 @@
|
||||||
|
|
||||||
(defn conj!
|
(defn conj!
|
||||||
"A conj! like function for js arrays."
|
"A conj! like function for js arrays."
|
||||||
[a v]
|
([a v]
|
||||||
(.push ^js a v)
|
(.push ^js a v)
|
||||||
a)
|
a)
|
||||||
|
([a v1 v2]
|
||||||
|
(.push ^js a v1 v2)
|
||||||
|
a)
|
||||||
|
([a v1 v2 v3]
|
||||||
|
(.push ^js a v1 v2 v3)
|
||||||
|
a)
|
||||||
|
([a v1 v2 v3 v4]
|
||||||
|
(.push ^js a v1 v2 v3 v4)
|
||||||
|
a)
|
||||||
|
([a v1 v2 v3 v4 v5]
|
||||||
|
(.push ^js a v1 v2 v3 v4 v5)
|
||||||
|
a)
|
||||||
|
([a v1 v2 v3 v4 v5 v6]
|
||||||
|
(.push ^js a v1 v2 v3 v4 v5 v6)
|
||||||
|
a))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue