0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 16:09:17 -05:00

🎉 Multiple selection rotation

This commit is contained in:
alonso.torres 2020-04-09 15:40:25 +02:00
parent e59b20fb6e
commit d86b5237c1
5 changed files with 146 additions and 104 deletions

View file

@ -1592,7 +1592,6 @@
(rx/of (diff-and-commit-changes page-id)
(rehash-shape-frame-relationship ids))))))
(defn apply-displacement-in-bulk
"Apply the same displacement delta to all shapes identified by the set
if ids."
@ -1696,6 +1695,54 @@
(update-in state [:workspace-data page-id :objects] merge shapes)))))
(defn apply-rotation
[delta-rotation shapes]
(ptk/reify ::apply-rotation
ptk/UpdateEvent
(update [_ state]
(let [group (geom/selection-rect shapes)
group-center (gpt/center group)
calculate-displacement
(fn [shape angle]
(let [shape-rect (geom/shape->rect-shape shape)
shape-center (gpt/center shape-rect)]
(-> (gmt/matrix)
(gmt/rotate angle group-center)
(gmt/rotate (- angle) shape-center))))
page-id (::page-id state)
rotate-shape
(fn [state shape]
(let [path [:workspace-data page-id :objects (:id shape)]
ds (calculate-displacement shape delta-rotation)]
(-> state
(assoc-in (conj path :rotation-modifier) delta-rotation)
(assoc-in (conj path :displacement-modifier) ds))))]
(reduce rotate-shape state shapes)))))
(defn materialize-rotation
[shapes]
(ptk/reify ::materialize-rotation
IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [apply-rotation
(fn [shape]
(let [ds-modifier (or (:displacement-modifier shape) (gmt/matrix))]
(-> shape
(update :rotation #(mod (+ % (:rotation-modifier shape)) 360))
(geom/transform ds-modifier)
(dissoc :rotation-modifier)
(dissoc :displacement-modifier))))
materialize-shape
(fn [state shape]
(let [path [:workspace-data (::page-id state) :objects (:id shape)]]
(update-in state path apply-rotation)))]
(reduce materialize-shape state shapes)))))
(defn commit-changes
([changes undo-changes] (commit-changes changes undo-changes {}))
([changes undo-changes {:keys [save-undo?

View file

@ -275,8 +275,7 @@
vid width height scalex scaley rotation))
(gmt/scale (gpt/point scalex scaley)
(gpt/point (cor-x shape)
(cor-y shape)))
)))
(cor-y shape))))))
(defn resize-shape
"Apply a resize transformation to a rect-like shape. The shape
@ -517,13 +516,16 @@
(transform shape (rotation-matrix shape)))
(defn resolve-modifier
[{:keys [resize-modifier displacement-modifier] :as shape}]
[{:keys [resize-modifier displacement-modifier rotation-modifier] :as shape}]
(cond-> shape
(gmt/matrix? resize-modifier)
(transform resize-modifier)
(gmt/matrix? displacement-modifier)
(transform displacement-modifier)))
(transform displacement-modifier)
rotation-modifier
(update :rotation #(+ (or % 0) rotation-modifier))))
;; NOTE: we need apply `shape->rect-shape` 3 times because we need to
;; update the x1 x2 y1 y2 attributes on each step; this is because
@ -652,9 +654,11 @@
([frame shape]
(let [ds-modifier (:displacement-modifier shape)
rz-modifier (:resize-modifier shape)
frame-ds-modifier (:displacement-modifier frame)]
frame-ds-modifier (:displacement-modifier frame)
rt-modifier (:rotation-modifier shape)]
(cond-> shape
(gmt/matrix? rz-modifier) (transform rz-modifier)
frame (move (gpt/point (- (:x frame)) (- (:y frame))))
(gmt/matrix? frame-ds-modifier) (transform frame-ds-modifier)
(gmt/matrix? ds-modifier) (transform ds-modifier)))))
(gmt/matrix? ds-modifier) (transform ds-modifier)
rt-modifier (update :rotation #(+ (or % 0) rt-modifier))))))

View file

@ -8,6 +8,8 @@
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.main.geom :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
@ -36,12 +38,10 @@
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation]} shape
transform (when (and rotation (pos? rotation))
(str/format "rotate(%s %s %s)"
rotation
(+ x (/ width 2))
(+ y (/ height 2))))
center (gpt/center shape)
transform (when (pos? rotation)
(str (-> (gmt/matrix)
(gmt/rotate rotation center))))
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
#js {:x x
@ -50,5 +50,4 @@
:id (str "shape-" id)
:width width
:height height}))]
[:> "rect" props]))

View file

@ -68,35 +68,39 @@
(rx/of (dw/materialize-resize-modifier-in-bulk ids))))))))
(defn start-rotate
[shape]
[shapes]
(ptk/reify ::start-rotate
ptk/WatchEvent
(watch [_ state stream]
(let [shape (geom/shape->rect-shape shape)
stoper (rx/filter ms/mouse-up? stream)
center (gpt/point (+ (:x shape) (/ (:width shape) 2))
(+ (:y shape) (/ (:height shape) 2)))]
(let [stoper (rx/filter ms/mouse-up? stream)
group (geom/selection-rect shapes)
group-center (gpt/center group)
initial-angle (gpt/angle @ms/mouse-position group-center)
calculate-angle (fn [pos ctrl?]
(let [angle (- (gpt/angle pos group-center) initial-angle)
angle (if (neg? angle) (+ 360 angle) angle)
modval (mod angle 90)
angle (if ctrl?
(if (< 50 modval)
(+ angle (- 90 modval))
(- angle modval))
angle)
angle (if (= angle 360)
0
angle)]
angle))]
(rx/concat
(->> ms/mouse-position
(rx/map apply-zoom)
(rx/with-latest vector ms/mouse-position-ctrl)
(rx/map (fn [[pos ctrl?]]
(let [angle (+ (gpt/angle pos center) 90)
angle (if (neg? angle)
(+ 360 angle)
angle)
modval (mod angle 90)
angle (if ctrl?
(if (< 50 modval)
(+ angle (- 90 modval))
(- angle modval))
angle)
angle (if (= angle 360)
0
angle)]
(dw/update-shape (:id shape) {:rotation angle}))))
(rx/take-until stoper)))))))
(let [delta-angle (calculate-angle pos ctrl?)]
(dw/apply-rotation delta-angle shapes))))
(rx/take-until stoper))
(rx/of (dw/materialize-rotation shapes))
)))))
;; --- Controls (Component)
@ -120,78 +124,67 @@
:cx cx
:cy cy}])
(def ^:private rotate-cursor-svg "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' transform='rotate(%s)' viewBox='0 0 132.292 132.006'%3E%3Cpath d='M85.225 3.48c.034 4.989-.093 9.852-.533 14.78-29.218 5.971-54.975 27.9-63.682 56.683-1.51 2.923-1.431 7.632-3.617 9.546-5.825.472-11.544.5-17.393.45 11.047 15.332 20.241 32.328 32.296 46.725 5.632 1.855 7.155-5.529 10.066-8.533 8.12-12.425 17.252-24.318 24.269-37.482-6.25-.86-12.564-.88-18.857-1.057 5.068-17.605 19.763-31.81 37.091-37.122.181 6.402.206 12.825 1.065 19.184 15.838-9.05 30.899-19.617 45.601-30.257 2.985-4.77-3.574-7.681-6.592-9.791C111.753 17.676 98.475 8.889 85.23.046l-.005 3.435z'/%3E%3Cpath fill='%23fff' d='M92.478 23.995s-1.143.906-6.714 1.923c-29.356 5.924-54.352 30.23-59.717 59.973-.605 3.728-1.09 5.49-1.09 5.49l-11.483-.002s7.84 10.845 10.438 15.486c3.333 4.988 6.674 9.971 10.076 14.912a2266.92 2266.92 0 0019.723-29.326c-5.175-.16-10.35-.343-15.522-.572 3.584-27.315 26.742-50.186 53.91-54.096.306 5.297.472 10.628.631 15.91a2206.462 2206.462 0 0029.333-19.726c-9.75-6.7-19.63-13.524-29.483-20.12z'/%3E%3C/svg%3E\") 10 10, auto")
(mf/defc rotation-handler
[{:keys [cx cy position on-mouse-down rotation]}]
(when (#{:top-left :top-right :bottom-left :bottom-right} position)
(let [size 20
rotation (or rotation 0)
x (- cx (if (#{:top-left :bottom-left} position) size 0))
y (- cy (if (#{:top-left :top-right} position) size 0))
angle (case position
:top-left 0
:top-right 90
:bottom-right 180
:bottom-left 270)]
[:rect {:style {:cursor (str/format rotate-cursor-svg (str (+ rotation angle)))}
:x x
:y y
:width size
:height size
:fill "transparent"
:on-mouse-down (or on-mouse-down (fn []))}])))
(mf/defc controls
[{:keys [shape zoom on-resize on-rotate] :as props}]
(let [{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
radius (if (> (max width height) handler-size-threshold) 6.0 4.0)
transform (geom/rotation-matrix shape)]
transform (geom/rotation-matrix shape)
resize-handlers {:top [(+ x (/ width 2 )) (- y 2)]
:right [(+ x width 1) (+ y (/ height 2))]
:bottom [(+ x (/ width 2)) (+ y height 2)]
:left [(- x 3) (+ y (/ height 2))]
:top-left [x y]
:top-right [(+ x width) y]
:bottom-left [x (+ y height)]
:bottom-right [(+ x width) (+ y height)]}]
[:g.controls {:transform transform}
[:rect.main {:x x :y y
:width width
:height height
:stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom))
:style {:stroke "#31EFB8" :fill "transparent"
:style {:stroke "#31EFB8"
:fill "transparent"
:stroke-opacity "1"}}]
(when (and (fn? on-rotate)
(not= :frame (:type shape)))
[:*
[:path {:stroke "#31EFB8"
:stroke-opacity "1"
:stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom))
:fill "transparent"
:d (str/format "M %s %s L %s %s"
(+ x (/ width 2))
y
(+ x (/ width 2))
(- y 30))}]
(for [[position [cx cy]] resize-handlers]
[:* {:key (str "fragment-" (name position))}
[:& rotation-handler {:key (str "rotation-" (name position))
:cx cx
:cy cy
:position position
:rotation (:rotation shape)
:on-mouse-down on-rotate}]
[:& control-item {:class "rotate"
[:& control-item {:key (str "resize-" (name position))
:class (name position)
:on-click #(on-resize position %)
:r (/ radius zoom)
:cx (+ x (/ width 2))
:on-click on-rotate
:cy (- y 30)}]])
[:& control-item {:class "top"
:on-click #(on-resize :top %)
:r (/ radius zoom)
:cx (+ x (/ width 2))
:cy (- y 2)}]
[:& control-item {:on-click #(on-resize :right %)
:r (/ radius zoom)
:cy (+ y (/ height 2))
:cx (+ x width 1)
:class "right"}]
[:& control-item {:on-click #(on-resize :bottom %)
:r (/ radius zoom)
:cx (+ x (/ width 2))
:cy (+ y height 2)
:class "bottom"}]
[:& control-item {:on-click #(on-resize :left %)
:r (/ radius zoom)
:cy (+ y (/ height 2))
:cx (- x 3)
:class "left"}]
[:& control-item {:on-click #(on-resize :top-left %)
:r (/ radius zoom)
:cx x
:cy y
:class "top-left"}]
[:& control-item {:on-click #(on-resize :top-right %)
:r (/ radius zoom)
:cx (+ x width)
:cy y
:class "top-right"}]
[:& control-item {:on-click #(on-resize :bottom-left %)
:r (/ radius zoom)
:cx x
:cy (+ y height)
:class "bottom-left"}]
[:& control-item {:on-click #(on-resize :bottom-right %)
:r (/ radius zoom)
:cx (+ x width)
:cy (+ y height)
:class "bottom-right"}]]))
:cx cx
:cy cy}]])]))
;; --- Selection Handlers (Component)
@ -253,7 +246,7 @@
(st/emit! (start-resize %1 selected shape)))
on-rotate #(do (dom/stop-propagation %)
(println "ROTATE!"))]
(st/emit! (start-rotate shapes)))]
[:& controls {:shape shape
:zoom zoom
@ -262,17 +255,11 @@
(mf/defc single-selection-handlers
[{:keys [shape zoom objects] :as props}]
(let [on-resize #(do (dom/stop-propagation %2)
(let [shape (geom/transform-shape shape)
on-resize #(do (dom/stop-propagation %2)
(st/emit! (start-resize %1 #{(:id shape)} shape)))
on-rotate #(do (dom/stop-propagation %)
(st/emit! (start-rotate shape)))
ds-modifier (:displacement-modifier shape)
rz-modifier (:resize-modifier shape)
;; shape (geom/resolve-shape objects shape)
shape (cond-> (geom/shape->rect-shape shape)
(gmt/matrix? rz-modifier) (geom/transform rz-modifier)
(gmt/matrix? ds-modifier) (geom/transform ds-modifier))]
(st/emit! (start-rotate [shape])))]
[:& controls {:shape shape
:zoom zoom

View file

@ -36,6 +36,11 @@
(throw (ex-info "Invalid arguments" {:v v}))))
([x y] (Point. x y)))
(defn center
[{:keys [x y width height]}]
(point (+ x (/ width 2))
(+ y (/ height 2))))
(defn add
"Returns the addition of the supplied value to both
coordinates of the point as a new point."