From f199317f473ee468bbb480002810f9948a7760c7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 21 May 2020 10:50:28 +0200 Subject: [PATCH] :sparkles: Improved selection handlers --- .../styles/main/partials/workspace.scss | 16 +- frontend/src/uxbox/main/data/workspace.cljs | 2 +- .../uxbox/main/data/workspace/transforms.cljs | 14 +- .../uxbox/main/ui/workspace/selection.cljs | 263 +++++++++++------- frontend/src/uxbox/util/debug.cljs | 2 +- 5 files changed, 176 insertions(+), 121 deletions(-) diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index ea0e7be7b..56494be88 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -146,14 +146,14 @@ g.controls { rect.main { pointer-events: none; } circle.rotate { cursor: ns-resize; } // TODO - circle.top-left { cursor: nwse-resize; } - circle.bottom-right { cursor: nwse-resize; } - circle.top-right { cursor: nesw-resize; } - circle.bottom-left { cursor: nesw-resize; } - circle.top { cursor: ns-resize; } - circle.bottom { cursor: ns-resize; } - circle.left { cursor: ew-resize; } - circle.right { cursor: ew-resize; } + .top-left { cursor: nwse-resize; } + .bottom-right { cursor: nwse-resize; } + .top-right { cursor: nesw-resize; } + .bottom-left { cursor: nesw-resize; } + .top { cursor: ns-resize; } + .bottom { cursor: ns-resize; } + .left { cursor: ew-resize; } + .right { cursor: ew-resize; } } } diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 482bea27f..ea9c2cc74 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1519,7 +1519,7 @@ {"ctrl+m" #(st/emit! (toggle-layout-flag :sitemap)) "ctrl+i" #(st/emit! (toggle-layout-flag :libraries)) "ctrl+l" #(st/emit! (toggle-layout-flag :layers)) - "ctrl+r" #(st/emit! (toggle-layout-flag :rules)) + "ctrl+shift+r" #(st/emit! (toggle-layout-flag :rules)) "ctrl+a" #(st/emit! (toggle-layout-flag :dynamic-alignment)) "ctrl+p" #(st/emit! (toggle-layout-flag :colorpalette)) "ctrl+'" #(st/emit! (toggle-layout-flag :display-grid)) diff --git a/frontend/src/uxbox/main/data/workspace/transforms.cljs b/frontend/src/uxbox/main/data/workspace/transforms.cljs index 52069905b..3392c35d7 100644 --- a/frontend/src/uxbox/main/data/workspace/transforms.cljs +++ b/frontend/src/uxbox/main/data/workspace/transforms.cljs @@ -73,18 +73,6 @@ (defn finish-transform [state] (update state :workspace-local dissoc :transform)) -(defn handler->initial-point [{:keys [x1 y1 x2 y2] :as shape} handler] - (let [[x y] (case handler - :top-left [x1 y1] - :top [x1 y1] - :top-right [x2 y1] - :right [x2 y1] - :bottom-right [x2 y2] - :bottom [x2 y2] - :bottom-left [x1 y2] - :left [x1 y2])] - (gpt/point x y))) - ;; -- RESIZE (defn start-resize [handler ids shape] @@ -142,7 +130,7 @@ ptk/WatchEvent (watch [_ state stream] (let [shape (gsh/shape->rect-shape shape) - initial (handler->initial-point shape handler) + initial @ms/mouse-position stoper (rx/filter ms/mouse-up? stream) page-id (get state :current-page-id) resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids) diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index c9d74d467..ee16fc11c 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -13,6 +13,7 @@ [cuerdas.core :as str] [potok.core :as ptk] [rumext.alpha :as mf] + [rumext.util :refer [map->obj]] [uxbox.main.data.workspace :as dw] [uxbox.util.geom.shapes :as geom] [uxbox.main.refs :as refs] @@ -24,70 +25,151 @@ [uxbox.util.geom.matrix :as gmt] [uxbox.util.debug :refer [debug?]])) -;; --- Controls (Component) - -(def ^:private handler-size-threshold - "The size in pixels that shape width or height - should reach in order to increase the handler - control pointer radius from 4 to 6." - 60) - -(mf/defc control-item - {::mf/wrap-props false} - [props] - (let [class (obj/get props "class") - on-click (obj/get props "on-click") - r (obj/get props "r") - cx (obj/get props "cx") - cy (obj/get props "cy")] - [:circle - {:class-name class - :on-mouse-down on-click - :r r - :style {:fillOpacity "1" - :strokeWidth "1px" - :vectorEffect "non-scaling-stroke"} - :fill "#ffffff" - :stroke "#1FDEA7" - :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") - -(defn rotation-cursor - [angle] +(defn rotation-cursor [angle] (str "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' transform='rotate(" angle ")' 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")) -(def rotation-handler-positions - #{:top-left :top-right :bottom-left :bottom-right}) +(def rotation-handler-size 25) +(def resize-point-radius 4) +(def resize-point-circle-radius 10) +(def resize-point-rect-size 20) +(def resize-side-height 8) +(def selection-rect-color "#1FDEA7") +(def selection-rect-width 1) -(mf/defc rotation-handler - {::mf/wrap-props false} - [props] - (let [cx (obj/get props "cx") - cy (obj/get props "cy") - position (obj/get props "position") - on-mouse-down (obj/get props "on-mouse-down") - rotation (obj/get props "rotation") - zoom (obj/get props "zoom")] - (when (contains? rotation-handler-positions position) - (let [size (/ 20 zoom) - 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 (rotation-cursor (+ rotation angle))} - :x x - :y y - :width size - :height size - :fill (if (debug? :rotation-handler) "red" "transparent") - :transform (gmt/rotate-matrix rotation (gpt/point cx cy)) - :on-mouse-down (or on-mouse-down (fn []))}])))) +(mf/defc selection-rect [{:keys [transform rect zoom]}] + (let [{:keys [x y width height]} rect] + [:rect.main + {:x x + :y y + :width width + :height height + :transform transform + :style {:stroke selection-rect-color + :stroke-width (/ selection-rect-width zoom) + :fill "transparent"}}])) + +(defn- handlers-for-selection [{:keys [x y width height]}] + [;; TOP-LEFT + {:type :rotation + :position :top-left + :props {:cx x :cy y}} + + {:type :resize-point + :position :top-left + :props {:cx x :cy y}} + + ;; TOP + {:type :resize-side + :position :top + :props {:x x :y y :length width :angle 0 }} + + ;; TOP-RIGHT + {:type :rotation + :position :top-right + :props {:cx (+ x width) :cy y}} + + {:type :resize-point + :position :top-right + :props {:cx (+ x width) :cy y}} + + ;; RIGHT + {:type :resize-side + :position :right + :props {:x (+ x width) :y y :length height :angle 90 }} + + ;; BOTTOM-RIGHT + {:type :rotation + :position :bottom-right + :props {:cx (+ x width) :cy (+ y height)}} + + {:type :resize-point + :position :bottom-right + :props {:cx (+ x width) :cy (+ y height)}} + + ;; BOTTOM + {:type :resize-side + :position :bottom + :props {:x (+ x width) :y (+ y height) :length width :angle 180 }} + + ;; BOTTOM-LEFT + {:type :rotation + :position :bottom-left + :props {:cx x :cy (+ y height)}} + + {:type :resize-point + :position :bottom-left + :props {:cx x :cy (+ y height)}} + + ;; LEFT + {:type :resize-side + :position :left + :props {:x x :y (+ y height) :length height :angle 270 }}]) + +(mf/defc rotation-handler [{:keys [cx cy transform position rotation zoom on-rotate]}] + (let [size (/ rotation-handler-size zoom) + 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 (rotation-cursor (+ rotation angle))} + :x x + :y y + :width size + :height size + :fill (if (debug? :rotation-handler) "blue" "transparent") + :transform transform + :on-mouse-down on-rotate}])) + +(mf/defc resize-point-handler [{:keys [cx cy zoom position on-resize transform]}] + (let [{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform) + rot-square (case position + :top-left 0 + :top-right 90 + :bottom-right 180 + :bottom-left 270)] + [:g.resize-handler + [:circle {:class (name position) + :r (/ resize-point-radius zoom) + :style {:fillOpacity "1" + :strokeWidth "1px" + :vectorEffect "non-scaling-stroke"} + :fill "#FFFFFF" + :stroke "#1FDEA7" + :cx cx' + :cy cy'}] + + [:rect {:class (name position) + :x cx + :y cy + :width (/ resize-point-rect-size zoom) + :height (/ resize-point-rect-size zoom) + :fill (if (debug? :resize-handler) "red" "transparent") + :on-mouse-down on-resize + :transform (gmt/multiply transform + (gmt/rotate-matrix rot-square (gpt/point cx cy)))}] + [:circle {:class (name position) + :on-mouse-down on-resize + :r (/ resize-point-circle-radius zoom) + :fill (if (debug? :resize-handler) "red" "transparent") + :cx cx' + :cy cy'}] + ])) + +(mf/defc resize-side-handler [{:keys [x y length angle zoom position transform on-resize]}] + [:rect {:x (+ x (/ resize-point-rect-size zoom)) + :y (- y (/ resize-side-height 2 zoom)) + :width (- length (/ (* resize-point-rect-size 2) zoom)) + :height (/ resize-side-height zoom) + :transform (gmt/multiply transform + (gmt/rotate-matrix angle (gpt/point x y))) + :on-mouse-down on-resize + :style {:fill (if (debug? :resize-handler) "yellow" "transparent") + :cursor (if (#{:left :right} position) + "ew-resize" + "ns-resize") }}]) (mf/defc controls {::mf/wrap-props false} @@ -98,46 +180,31 @@ on-rotate (obj/get props "on-rotate") current-transform (mf/deref refs/current-transform) - {:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape) + selrect (geom/shape->rect-shape shape) + transform (geom/transform-matrix shape)] - radius (if (> (max width height) handler-size-threshold) 4.0 4.0) - transform (geom/transform-matrix shape) - resize-handlers {:top [(+ x (/ width 2 )) y] - :right [(+ x width) (+ y (/ height 2))] - :bottom [(+ x (/ width 2)) (+ y height)] - :left [x (+ y (/ height 2))] - :top-left [x y] - :top-right [(+ x width) y] - :bottom-left [x (+ y height)] - :bottom-right [(+ x width) (+ y height)]}] + (when (not (#{:move :rotate} current-transform)) + [:g.controls - [:g.controls - (when (not (#{:move :rotate} current-transform)) - [:rect.main - {:transform transform - :x x :y y - :width width - :height height - :style {:stroke "#1FDEA7" - :stroke-width (/ 1 zoom) - :fill "transparent"}}]) + ;; Selection rect + [:& selection-rect {:rect shape + :transform transform + :zoom zoom}] - (when (not (#{:move :rotate} current-transform)) - (for [[position [cx cy]] resize-handlers] - (let [tp (gpt/transform (gpt/point cx cy) transform)] - [:* {:key (name position)} - [:& rotation-handler {:cx (:x tp) - :cy (:y tp) - :position position - :rotation (:rotation shape) - :zoom zoom - :on-mouse-down on-rotate}] - - [:& control-item {:class (name position) - :on-click #(on-resize position %) - :r (/ radius zoom) - :cx (:x tp) - :cy (:y tp)}]])))])) + ;; Handlers + (for [{:keys [type position props]} (handlers-for-selection selrect)] + (let [common-props {:key (str (name type) "-" (name position)) + :zoom zoom + :position position + :on-rotate on-rotate + :on-resize (partial on-resize position) + :transform transform + :rotation (:rotation shape)} + props (->> props (merge common-props) map->obj)] + (case type + :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) + :resize-point [:> resize-point-handler props] + :resize-side [:> resize-side-handler props])))]))) ;; --- Selection Handlers (Component) (mf/defc path-edition-selection-handlers diff --git a/frontend/src/uxbox/util/debug.cljs b/frontend/src/uxbox/util/debug.cljs index 259905cdc..a6eae4df4 100644 --- a/frontend/src/uxbox/util/debug.cljs +++ b/frontend/src/uxbox/util/debug.cljs @@ -1,7 +1,7 @@ (ns uxbox.util.debug "Debugging utils") -(def debug-options #{:bounding-boxes :group :events :rotation-handler :selection-center #_:simple-selection }) +(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center #_:simple-selection }) (defonce ^:dynamic *debug* (atom #{}))