diff --git a/frontend/src/uxbox/main/data/workspace/transforms.cljs b/frontend/src/uxbox/main/data/workspace/transforms.cljs index 43b5d61fc..e051e236c 100644 --- a/frontend/src/uxbox/main/data/workspace/transforms.cljs +++ b/frontend/src/uxbox/main/data/workspace/transforms.cljs @@ -65,19 +65,21 @@ :bottom-left [ex sy])] (gpt/point x y))) +(defn finish-transform [state] + (update state :workspace-local dissoc :transform)) + ;; -- RESIZE (defn start-resize [handler ids shape] - (letfn [(resize [shape initial [point lock?]] + (letfn [(resize [shape initial resizing-shapes snap-data [point lock?]] (let [{:keys [width height rotation]} shape - shapev (-> (gpt/point width height)) ;; Vector modifiers depending on the handler handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y)) ;; Difference between the origin point in the coordinate system of the rotation - deltav (-> (gpt/subtract point initial) + deltav (-> (snap/closest-snap snap-data resizing-shapes (gpt/to-vec initial point)) (gpt/transform (gmt/rotate-matrix (- rotation))) (gpt/multiply handler-modif)) @@ -115,27 +117,41 @@ ;; (rx/of point))) ] (reify + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :transform] :resize))) + ptk/WatchEvent (watch [_ state stream] (let [initial (apply-zoom @ms/mouse-position) shape (gsh/shape->rect-shape shape) - stoper (rx/filter ms/mouse-up? stream)] + stoper (rx/filter ms/mouse-up? stream) + snap-data (get state :workspace-snap-data) + page-id (get state :current-page-id) + resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids)] (rx/concat (->> ms/mouse-position (rx/map apply-zoom) ;; (rx/mapcat apply-grid-alignment) (rx/with-latest vector ms/mouse-position-ctrl) (rx/map normalize-proportion-lock) - (rx/mapcat (partial resize shape initial)) + (rx/mapcat (partial resize shape initial resizing-shapes snap-data)) (rx/take-until stoper)) #_(rx/empty) - (rx/of (apply-modifiers ids)))))))) + (rx/of (apply-modifiers ids) + finish-transform))))))) ;; -- ROTATE (defn start-rotate [shapes] (ptk/reify ::start-rotate + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :transform] :rotate))) + ptk/WatchEvent (watch [_ state stream] (let [stoper (rx/filter ms/mouse-up? stream) @@ -163,13 +179,19 @@ (let [delta-angle (calculate-angle pos ctrl?)] (set-rotation delta-angle shapes group-center)))) (rx/take-until stoper)) - (rx/of (apply-modifiers (map :id shapes)))))))) + (rx/of (apply-modifiers (map :id shapes)) + finish-transform)))))) ;; -- MOVE (defn start-move-selected [] (ptk/reify ::start-move-selected + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc-in [:workspace-local :transform] :move))) + ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected]) @@ -192,11 +214,14 @@ (rx/map #(set-modifiers selected {:displacement %})) (rx/tap #(vswap! counter inc)) (rx/take-until stoper)) - (->> (rx/create (fn [sink] (sink @counter))) + (->> (rx/create (fn [sink] (sink (reduced @counter)))) (rx/mapcat (fn [n] (if (zero? n) (rx/empty) - (rx/of (apply-modifiers selected))))))))))) + (rx/of (apply-modifiers selected)))))) + + (rx/of finish-transform) + ))))) (defn- get-displacement-with-grid "Retrieve the correct displacement delta point for the diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 9693ca464..1f06e8760 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -93,6 +93,15 @@ (def selected-shapes (l/derived :selected workspace-local)) +(def selected-shapes-with-children + (letfn [(selector [state] + (let [selected (get-in state [:workspace-local :selected]) + page-id (get-in state [:workspace-page :id]) + objects (get-in state [:workspace-data page-id :objects]) + children (mapcat #(helpers/get-children % objects) selected)] + (into selected children)))] + (l/derived selector st/state))) + (defn make-selected [id] (l/derived #(contains? % id) selected-shapes)) @@ -105,3 +114,6 @@ (def selected-edition (l/derived :edition workspace-local)) + +(def current-transform + (l/derived :transform workspace-local)) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index 7c43e52f4..23c07512d 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -119,8 +119,8 @@ (let [shape (get-in state [:workspace-local :drawing]) shape (geom/setup shape {:x (:x point) :y (:y point) - :width 10 - :height 10})] + :width 1 + :height 1})] (assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true)))) (resize-shape [{:keys [x y] :as shape} initial point lock?] @@ -280,8 +280,16 @@ (rx/concat (rx/of dw/clear-drawing) (when (::initialized? shape) - (let [shape (-> shape + (let [shape-min-width (case (:type shape) + :text 20 + 5) + shape-min-height (case (:type shape) + :text 16 + 5) + shape (-> shape (geom/transform-shape) + (update :width #(max shape-min-width %)) + (update :height #(max shape-min-height %)) (dissoc shape ::initialized?))] ;; Add & select the created shape to the workspace (rx/of dw/deselect-all diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 8e2c702e6..b67ba6d2a 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -22,7 +22,6 @@ [uxbox.util.object :as obj] [uxbox.util.geom.point :as gpt] [uxbox.util.geom.matrix :as gmt] - [uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]] [uxbox.util.debug :refer [debug?]])) ;; --- Controls (Component) @@ -97,7 +96,7 @@ zoom (obj/get props "zoom") on-resize (obj/get props "on-resize") 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) radius (if (> (max width height) handler-size-threshold) 4.0 4.0) @@ -112,23 +111,26 @@ :bottom-right [(+ x width) (+ y height)]}] [:g.controls - [:rect.main {:transform transform - :x (- x 1) :y (- y 1) - :width (+ width 2) - :height (+ height 2) - :style {:stroke "#1FDEA7" - :stroke-width "1" - :fill "transparent"}}] + (when (not (#{:move :rotate :resize} current-transform)) + [:rect.main {:transform transform + :x (- x 1) :y (- y 1) + :width (+ width 2) + :height (+ height 2) + :style {:stroke "#1FDEA7" + :stroke-width "1" + :fill "transparent"}}]) - (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}] + (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 {:key (str "rotation-" (name position)) + :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 %) @@ -195,7 +197,6 @@ [{:keys [shapes selected zoom] :as props}] (let [shape (geom/selection-rect shapes) shape-center (geom/center shape) - on-resize #(do (dom/stop-propagation %2) (st/emit! (dw/start-resize %1 selected shape))) @@ -207,7 +208,6 @@ :zoom zoom :on-resize on-resize :on-rotate on-rotate}] - [:& snap-feedback {:shapes shapes}] (when (debug? :selection-center) [:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])])) @@ -229,8 +229,7 @@ [:& controls {:shape shape' :zoom zoom :on-rotate on-rotate - :on-resize on-resize}] - [:& snap-feedback {:shapes [shape]}]])) + :on-resize on-resize}]])) (mf/defc selection-handlers [{:keys [selected edition zoom] :as props}] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/.#circle.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/.#circle.cljs deleted file mode 120000 index d6a744794..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/.#circle.cljs +++ /dev/null @@ -1 +0,0 @@ -alotor@bloodraven.68367:1587963441 \ No newline at end of file diff --git a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs index cdda9bb42..f40016ead 100644 --- a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs +++ b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs @@ -5,44 +5,43 @@ [uxbox.util.geom.snap :as snap] [uxbox.util.geom.point :as gpt])) - (def ^:private line-color "#D383DA") -(mf/defc snap-feedback - [{:keys [shapes] :as props}] - (let [snap-data (mf/deref refs/workspace-snap-data)] - (for [shape shapes] - (for [point (snap/shape-snap-points shape)] - (let [frame-id (:frame-id shape) - shape-id (:id shape) +(mf/defc snap-feedback [] + (let [selected (mf/deref refs/selected-shapes) + shapes (mf/deref (refs/objects-by-id selected)) + filter-shapes (mf/deref refs/selected-shapes-with-children) + current-transform (mf/deref refs/current-transform) + snap-data (mf/deref refs/workspace-snap-data)] + (when (not (nil? current-transform)) + (for [shape shapes] + (for [point (snap/shape-snap-points shape)] + (let [frame-id (:frame-id shape) + shape-id (:id shape) + snaps (into #{} + (concat + (snap/get-snap-points snap-data frame-id filter-shapes point :x) + (snap/get-snap-points snap-data frame-id filter-shapes point :y)))] + (if (not-empty snaps) + [:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))} + [:circle {:cx (:x point) + :cy (:y point) + :r 2 + :fill line-color}] - snaps-x (snap/get-snap-points snap-data frame-id shape-id point :x) - snaps-y (snap/get-snap-points snap-data frame-id shape-id point :y)] - (if (or (not-empty snaps-x) (not-empty snaps-y)) - [:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))} - [:circle {:cx (:x point) - :cy (:y point) - :r 2 - :fill line-color}] - - (for [snap (concat snaps-x snaps-y)] - [:* - [:circle {:cx (:x snap) + (for [snap snaps] + [:circle {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap)) + :cx (:x snap) :cy (:y snap) :r 2 - :fill line-color}] - [:line {:x1 (:x snap) + :fill line-color}]) + + (for [snap snaps] + [:line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap)) + :x1 (:x snap) :y1 (:y snap) :x2 (:x point) :y2 (:y point) :style {:stroke line-color :stroke-width "1"} - :opacity 0.4}]]) - - #_(when is-snap-y? - [:line {:x1 -10000 - :y1 (:y point) - :x2 10000 - :y2 (:y point) - :style {:stroke line-color :stroke-width "1"} - :opacity 0.4}])])))))) + :opacity 0.4}])]))))))) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index c01bbe909..91a3d81c1 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -29,6 +29,7 @@ [uxbox.main.ui.workspace.ruler :refer [ruler]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]] [uxbox.main.ui.workspace.presence :as presence] + [uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]] [uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt] [uxbox.util.perf :as perf] @@ -305,6 +306,8 @@ :zoom zoom :edition edition}]) + [:& snap-feedback] + (when-let [drawing-shape (:drawing local)] [:& draw-area {:shape drawing-shape :zoom zoom diff --git a/frontend/src/uxbox/util/geom/point.cljs b/frontend/src/uxbox/util/geom/point.cljs index 0e2d9b7f3..de1b3744d 100644 --- a/frontend/src/uxbox/util/geom/point.cljs +++ b/frontend/src/uxbox/util/geom/point.cljs @@ -8,8 +8,9 @@ ;; Copyright (c) 2015-2020 Andrey Antukh (ns uxbox.util.geom.point - (:refer-clojure :exclude [divide]) + (:refer-clojure :exclude [divide min max]) (:require + [cljs.core :as c] [cuerdas.core :as str] [uxbox.util.math :as mth] [cognitect.transit :as t])) @@ -70,6 +71,15 @@ (assert (point? other)) (Point. (/ x ox) (/ y oy))) + +(defn min + [{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + (Point. (c/min x1 x2) (c/min y1 y2))) + +(defn max + [{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + (Point. (c/max x1 x2) (c/max y1 y2))) + (defn inverse [{:keys [x y] :as p}] (assert (point? p)) diff --git a/frontend/src/uxbox/util/geom/shapes.cljs b/frontend/src/uxbox/util/geom/shapes.cljs index 4634615df..6755f9a3e 100644 --- a/frontend/src/uxbox/util/geom/shapes.cljs +++ b/frontend/src/uxbox/util/geom/shapes.cljs @@ -738,6 +738,15 @@ (gpt/divide (gpt/point (:width shape-path-temp-rec) (:height shape-path-temp-rec)) (gpt/point (:width shape-path-temp-dim) (:height shape-path-temp-dim))))) +(defn- fix-invalid-rect-values [rect-shape] + (letfn [(check [num] (if (or (nil? num) (mth/nan? num)) 0 num)) + (to-positive [num] (if (< num 1) 1 num))] + (-> rect-shape + (update :x check) + (update :y check) + (update :width (comp to-positive check)) + (update :height (comp to-positive check))))) + (defn transform-rect-shape [shape] (let [;; Apply modifiers to the rect as a path so we have the end shape expected @@ -785,6 +794,7 @@ (merge rec) (update :x #(mth/precision % 2)) (update :y #(mth/precision % 2)) + (fix-invalid-rect-values) (update :transform #(gmt/multiply (or % (gmt/matrix)) stretch-matrix)) (update :transform-inverse #(gmt/multiply stretch-matrix-inverse (or % (gmt/matrix)))))] diff --git a/frontend/src/uxbox/util/geom/snap.cljs b/frontend/src/uxbox/util/geom/snap.cljs index 5fb4a3657..4a6c074aa 100644 --- a/frontend/src/uxbox/util/geom/snap.cljs +++ b/frontend/src/uxbox/util/geom/snap.cljs @@ -16,17 +16,29 @@ [uxbox.util.geom.point :as gpt] [uxbox.util.debug :refer [logjs]])) -(def ^:private snap-accuracy 5) +(def ^:private snap-accuracy 8) (defn mapm "Map over the values of a map" [mfn coll] (into {} (map (fn [[key val]] [key (mfn val)]) coll))) +(defn- frame-snap-points [{:keys [x y width height]}] + #{(gpt/point x y) + (gpt/point (+ x (/ width 2)) y) + (gpt/point (+ x width) y) + (gpt/point (+ x width) (+ y (/ height 2))) + (gpt/point (+ x width) (+ y height)) + (gpt/point (+ x (/ width 2)) (+ y height)) + (gpt/point x (+ y height)) + (gpt/point x (+ y (/ height 2)))}) + (defn shape-snap-points [shape] - (let [modified-path (gsh/transform-apply-modifiers shape) - shape-center (gsh/center modified-path)] - (into #{shape-center} (:segments modified-path)))) + (if (= :frame (:type shape)) + (frame-snap-points shape) + (let [modified-path (gsh/transform-apply-modifiers shape) + shape-center (gsh/center modified-path)] + (into #{shape-center} (:segments modified-path))))) (defn create-coord-data [shapes coord] (let [process-shape @@ -42,7 +54,14 @@ "Initialize the snap information with the current workspace information" [objects] (let [shapes (vals objects) - frame-shapes (group-by :frame-id (filter (comp not nil? :frame-id) shapes))] + frame-shapes (->> shapes + (filter (comp not nil? :frame-id)) + (group-by :frame-id)) + + frame-shapes (->> shapes + (filter #(= :frame (:type %))) + (remove #(= zero (:id %))) + (reduce #(update %1 (:id %2) conj %2) frame-shapes))] (logjs "snap-data" (mapm (fn [shapes] {:x (create-coord-data shapes :x) :y (create-coord-data shapes :y)}) @@ -125,13 +144,13 @@ (gpt/add trans-vec snapv)))) -(defn get-snap-points [snap-data frame-id shape-id point coord] +(defn get-snap-points [snap-data frame-id filter-shapes point coord] (let [value (coord point) ;; Search for values within 1 pixel snap-matches (-> (get-in snap-data [frame-id coord]) (range-query (- value 0.5) (+ value 0.5)) - (remove-from-snap-points #{shape-id})) + (remove-from-snap-points filter-shapes)) snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)] snap-points)) @@ -139,5 +158,5 @@ (defn is-snapping? [snap-data frame-id shape-id point coord] (let [value (coord point) ;; Search for values within 1 pixel - snap-points (range-query (get-in snap-data [frame-id coord]) (- value 0.25) (+ value 0.25))] + snap-points (range-query (get-in snap-data [frame-id coord]) (- value 1.0) (+ value 1.0))] (some (fn [[point other-shape-id]] (not (= shape-id other-shape-id))) snap-points)))