diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 28341c479..78bbf01b9 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -875,6 +875,8 @@ "en" : "Show rules" } }, + "workspace.header.menu.disable-dynamic-alignment": "Disable dynamic alignment", + "workspace.header.menu.enable-dynamic-alignment": "Enable dynamic aligment", "workspace.header.viewer" : { "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:153" ], "translations" : { diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index e07bf8c0b..0b2f345f5 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -32,7 +32,6 @@ [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] [uxbox.util.geom.shapes :as geom] - [uxbox.util.geom.snap :as snap] [uxbox.util.math :as mth] [uxbox.util.router :as rt] [uxbox.util.transit :as t] @@ -67,7 +66,7 @@ :layers :element-options :rules - }) + :dynamic-alignment}) (s/def ::options-mode #{:design :prototype}) @@ -1452,6 +1451,7 @@ "ctrl+shift+i" #(st/emit! (toggle-layout-flag :libraries)) "ctrl+shift+l" #(st/emit! (toggle-layout-flag :layers)) "ctrl+shift+r" #(st/emit! (toggle-layout-flag :rules)) + "ctrl+shift+d" #(st/emit! (toggle-layout-flag :dynamic-alignment)) "+" #(st/emit! increase-zoom) "-" #(st/emit! decrease-zoom) "ctrl+g" #(st/emit! create-group) diff --git a/frontend/src/uxbox/main/data/workspace/common.cljs b/frontend/src/uxbox/main/data/workspace/common.cljs index c130e43d1..624ce3fad 100644 --- a/frontend/src/uxbox/main/data/workspace/common.cljs +++ b/frontend/src/uxbox/main/data/workspace/common.cljs @@ -19,8 +19,7 @@ [uxbox.common.uuid :as uuid] [uxbox.main.worker :as uw] [uxbox.util.timers :as ts] - [uxbox.util.geom.shapes :as geom] - [uxbox.util.geom.snap :as snap])) + [uxbox.util.geom.shapes :as geom])) ;; --- Protocols diff --git a/frontend/src/uxbox/main/data/workspace/transforms.cljs b/frontend/src/uxbox/main/data/workspace/transforms.cljs index 2c4adb2be..59433ee9c 100644 --- a/frontend/src/uxbox/main/data/workspace/transforms.cljs +++ b/frontend/src/uxbox/main/data/workspace/transforms.cljs @@ -24,7 +24,7 @@ [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] [uxbox.util.geom.shapes :as gsh] - [uxbox.util.geom.snap :as snap])) + [uxbox.main.snap :as snap])) ;; -- Declarations @@ -145,14 +145,15 @@ initial (handler->initial-point shape handler) 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)] + resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids) + layout (get state :workspace-layout)] (rx/concat (->> ms/mouse-position ;; (rx/mapcat apply-grid-alignment) (rx/with-latest vector ms/mouse-position-ctrl) (rx/map normalize-proportion-lock) (rx/switch-map (fn [[point :as current]] - (->> (snap/closest-snap-point page-id resizing-shapes point) + (->> (snap/closest-snap-point page-id resizing-shapes layout point) (rx/map #(conj current %))))) (rx/mapcat (partial resize shape initial resizing-shapes)) (rx/take-until stoper)) @@ -231,12 +232,13 @@ (watch [_ state stream] (let [page-id (get state :current-page-id) shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids) - stopper (rx/filter ms/mouse-up? stream)] + stopper (rx/filter ms/mouse-up? stream) + layout (get state :workspace-layout)] (rx/concat (->> ms/mouse-position (rx/take-until stopper) (rx/map #(gpt/to-vec from-position %)) - (rx/switch-map #(snap/closest-snap-move page-id shapes %)) + (rx/switch-map #(snap/closest-snap-move page-id shapes layout %)) (rx/map gmt/translate-matrix) (rx/map #(set-modifiers ids {:displacement %}))) diff --git a/frontend/src/uxbox/util/geom/snap.cljs b/frontend/src/uxbox/main/snap.cljs similarity index 57% rename from frontend/src/uxbox/util/geom/snap.cljs rename to frontend/src/uxbox/main/snap.cljs index c1a83ea6b..70c0be0c9 100644 --- a/frontend/src/uxbox/util/geom/snap.cljs +++ b/frontend/src/uxbox/main/snap.cljs @@ -7,66 +7,27 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns uxbox.util.geom.snap +(ns uxbox.main.snap (:require - [cljs.spec.alpha :as s] - [clojure.set :as set] [beicon.core :as rx] - [uxbox.util.math :as mth] [uxbox.common.uuid :refer [zero]] - [uxbox.util.geom.shapes :as gsh] + [uxbox.util.math :as mth] [uxbox.util.geom.point :as gpt] - [uxbox.main.worker :as uw])) + [uxbox.main.worker :as uw] + [uxbox.util.geom.snap-points :as sp])) -(def ^:private snap-accuracy 10) +(def ^:private snap-accuracy 5) -(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- frame-snap-points-resize [{:keys [x y width height]} handler] - (case handler - :top-left (gpt/point x y) - :top (gpt/point (+ x (/ width 2)) y) - :top-right (gpt/point (+ x width) y) - :right (gpt/point (+ x width) (+ y (/ height 2))) - :bottom-right (gpt/point (+ x width) (+ y height)) - :bottom (gpt/point (+ x (/ width 2)) (+ y height)) - :bottom-left (gpt/point x (+ y height)) - :left (gpt/point x (+ y (/ height 2))))) - -(def ^:private handler->point-idx - {:top-left 0 - :top 0 - :top-right 1 - :right 1 - :bottom-right 2 - :bottom 2 - :bottom-left 3 - :left 3}) - -(defn shape-snap-points - [shape] - (let [modified-path (gsh/transform-apply-modifiers shape) - shape-center (gsh/center modified-path)] - (case (:type shape) - :frame (-> modified-path gsh/shape->rect-shape frame-snap-points) - (:path :curve) (into #{shape-center} (-> modified-path gsh/shape->rect-shape :segments)) - (into #{shape-center} (-> modified-path :segments))))) - - -(defn remove-from-snap-points [ids-to-remove] +(defn- remove-from-snap-points [ids-to-remove] (fn [query-result] (->> query-result (map (fn [[value data]] [value (remove (comp ids-to-remove second) data)])) (filter (fn [[_ data]] (not (empty? data))))))) +(defn- flatten-to-points + [query-result] + (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) query-result)) + (defn- calculate-distance [query-result point coord] (->> query-result (map (fn [[value data]] [(mth/abs (- value (coord point))) [(coord point) value]])))) @@ -78,7 +39,7 @@ (apply min-key first) second))) -(defn snap-frame-id [shapes] +(defn- snap-frame-id [shapes] (let [frames (into #{} (map :frame-id shapes))] (cond ;; Only shapes from one frame. The common is the only one @@ -90,10 +51,6 @@ ;; Otherwise the root frame is the common :else zero))) -(defn flatten-to-points - [query-result] - (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) query-result)) - (defn get-snap-points [page-id frame-id filter-shapes point coord] (let [value (coord point)] (->> (uw/ask! {:cmd :snaps/range-query @@ -132,21 +89,25 @@ (rx/combine-latest snap-as-vector snap-y snap-x))) (defn closest-snap-point - [page-id shapes point] - (let [frame-id (snap-frame-id shapes) - filter-shapes (into #{} (map :id shapes))] - (->> (closest-snap page-id frame-id [point] filter-shapes) - (rx/map #(gpt/add point %))))) + [page-id shapes layout point] + (if (layout :dynamic-alignment) + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes))] + (->> (closest-snap page-id frame-id [point] filter-shapes) + (rx/map #(gpt/add point %)))) + (rx/of point))) (defn closest-snap-move - [page-id shapes movev] - (let [frame-id (snap-frame-id shapes) - filter-shapes (into #{} (map :id shapes)) - shapes-points (->> shapes - ;; Unroll all the possible snap-points - (mapcat (partial shape-snap-points)) + [page-id shapes layout movev] + (if (layout :dynamic-alignment) + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes)) + shapes-points (->> shapes + ;; Unroll all the possible snap-points + (mapcat (partial sp/shape-snap-points)) - ;; Move the points in the translation vector - (map #(gpt/add % movev)))] - (->> (closest-snap page-id frame-id shapes-points filter-shapes) - (rx/map #(gpt/add movev %))))) + ;; Move the points in the translation vector + (map #(gpt/add % movev)))] + (->> (closest-snap page-id frame-id shapes-points filter-shapes) + (rx/map #(gpt/add movev %)))) + (rx/of movev))) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index c36ca7d96..ad0dcf91f 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -24,7 +24,7 @@ [uxbox.util.geom.path :as path] [uxbox.util.geom.point :as gpt] [uxbox.util.i18n :as i18n :refer [t]] - [uxbox.util.geom.snap :as snap] + [uxbox.main.snap :as snap] [uxbox.common.uuid :as uuid])) ;; --- Events @@ -144,6 +144,7 @@ page-id (get state :current-page-id) objects (get-in state [:workspace-data page-id :objects]) + layout (get state :workspace-layout) frames (->> objects vals @@ -167,7 +168,7 @@ (->> mouse (rx/with-latest vector ms/mouse-position-ctrl) (rx/switch-map (fn [[point :as current]] - (->> (snap/closest-snap-point page-id [shape] point) + (->> (snap/closest-snap-point page-id [shape] layout point) (rx/map #(conj current %))))) (rx/map (fn [[pt ctrl? point-snap]] #(update-drawing % initial pt ctrl? point-snap))) (rx/take-until stoper)) diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index d28f2c06f..d0f750b77 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -108,6 +108,13 @@ (if (contains? layout :libraries) (t locale "workspace.header.menu.hide-libraries") (t locale "workspace.header.menu.show-libraries"))]] + + [:li {:on-click #(st/emit! (dw/toggle-layout-flag :dynamic-alignment))} + [:span i/shape-halign-left] + [:span + (if (contains? layout :dynamic-alignment) + (t locale "workspace.header.menu.disable-dynamic-alignment") + (t locale "workspace.header.menu.enable-dynamic-alignment"))]] ]]])) ;; --- Header Component diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs index 6433aaf2f..ad707e253 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs @@ -10,7 +10,6 @@ [rumext.alpha :as mf] [uxbox.util.debug :as debug] [uxbox.util.geom.shapes :as geom] - [uxbox.util.geom.snap :as snap] [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] [uxbox.util.debug :refer [debug?]] diff --git a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs index b44351410..53e7289af 100644 --- a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs +++ b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs @@ -3,38 +3,39 @@ [rumext.alpha :as mf] [beicon.core :as rx] [uxbox.main.refs :as refs] - [uxbox.util.geom.snap :as snap] + [uxbox.main.snap :as snap] + [uxbox.util.geom.snap-points :as sp] [uxbox.util.geom.point :as gpt])) (def ^:private line-color "#D383DA") -(mf/defc snap-point [{:keys [point]}] +(mf/defc snap-point [{:keys [point zoom]}] (let [{:keys [x y]} point - cross-width 3] + cross-width (/ 3 zoom)] [:g [:line {:x1 (- x cross-width) :y1 (- y cross-width) :x2 (+ x cross-width) :y2 (+ y cross-width) - :style {:stroke line-color :stroke-width "1"}}] + :style {:stroke line-color :stroke-width (str (/ 1 zoom))}}] [:line {:x1 (- x cross-width) :y1 (+ y cross-width) :x2 (+ x cross-width) :y2 (- y cross-width) - :style {:stroke line-color :stroke-width "1"}}]])) + :style {:stroke line-color :stroke-width (str (/ 1 zoom))}}]])) -(mf/defc snap-line [{:keys [snap point]}] +(mf/defc snap-line [{:keys [snap point zoom]}] [:line {:x1 (:x snap) :y1 (:y snap) :x2 (:x point) :y2 (:y point) - :style {:stroke line-color :stroke-width "1"} + :style {:stroke line-color :stroke-width (str (/ 1 zoom))} :opacity 0.4}]) (defn get-snap [coord {:keys [shapes page-id filter-shapes]}] (->> (rx/from shapes) (rx/flat-map (fn [shape] - (->> (snap/shape-snap-points shape) + (->> (sp/shape-snap-points shape) (map #(vector (:frame-id shape) %))))) (rx/flat-map (fn [[frame-id point]] (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) @@ -42,7 +43,7 @@ (rx/reduce conj []))) (mf/defc snap-feedback-points - [{:keys [shapes page-id filter-shapes] :as props}] + [{:keys [shapes page-id filter-shapes zoom] :as props}] (let [state (mf/use-state []) subject (mf/use-memo #(rx/subject))] @@ -65,16 +66,19 @@ (if (not-empty snaps) [:g.point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord))} [:& snap-point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord)) - :point point}] + :point point + :zoom zoom}] (for [snap snaps] [:& snap-point {:key (str "snap-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord)) - :point snap}]) + :point snap + :zoom zoom}]) (for [snap snaps] [:& snap-line {:key (str "line-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord)) :snap snap - :point point}])]))])) + :point point + :zoom zoom}])]))])) (mf/defc snap-feedback [{:keys []}] (let [page-id (mf/deref refs/workspace-page-id) @@ -84,9 +88,11 @@ filter-shapes (mf/deref refs/selected-shapes-with-children) current-transform (mf/deref refs/current-transform) snap-data (mf/deref refs/workspace-snap-data) - shapes (if drawing [drawing] selected-shapes)] + shapes (if drawing [drawing] selected-shapes) + zoom (mf/deref refs/selected-zoom)] (when (or drawing current-transform) [:& snap-feedback-points {:shapes shapes :page-id page-id - :filter-shapes filter-shapes}]))) + :filter-shapes filter-shapes + :zoom zoom}]))) diff --git a/frontend/src/uxbox/util/geom/snap_points.cljs b/frontend/src/uxbox/util/geom/snap_points.cljs new file mode 100644 index 000000000..a8274a566 --- /dev/null +++ b/frontend/src/uxbox/util/geom/snap_points.cljs @@ -0,0 +1,55 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020 UXBOX Labs SL + +(ns uxbox.util.geom.snap-points + (:require + [cljs.spec.alpha :as s] + [clojure.set :as set] + [uxbox.util.geom.shapes :as gsh] + [uxbox.util.geom.point :as gpt])) + +(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- frame-snap-points-resize [{:keys [x y width height]} handler] + (case handler + :top-left (gpt/point x y) + :top (gpt/point (+ x (/ width 2)) y) + :top-right (gpt/point (+ x width) y) + :right (gpt/point (+ x width) (+ y (/ height 2))) + :bottom-right (gpt/point (+ x width) (+ y height)) + :bottom (gpt/point (+ x (/ width 2)) (+ y height)) + :bottom-left (gpt/point x (+ y height)) + :left (gpt/point x (+ y (/ height 2))))) + +(def ^:private handler->point-idx + {:top-left 0 + :top 0 + :top-right 1 + :right 1 + :bottom-right 2 + :bottom 2 + :bottom-left 3 + :left 3}) + +(defn shape-snap-points + [shape] + (let [modified-path (gsh/transform-apply-modifiers shape) + shape-center (gsh/center modified-path)] + (case (:type shape) + :frame (-> modified-path gsh/shape->rect-shape frame-snap-points) + (:path :curve) (into #{shape-center} (-> modified-path gsh/shape->rect-shape :segments)) + (into #{shape-center} (-> modified-path :segments))))) diff --git a/frontend/src/uxbox/worker/snaps.cljs b/frontend/src/uxbox/worker/snaps.cljs index b14b49afc..b1f7c70e1 100644 --- a/frontend/src/uxbox/worker/snaps.cljs +++ b/frontend/src/uxbox/worker/snaps.cljs @@ -14,7 +14,7 @@ [uxbox.common.pages :as cp] [uxbox.worker.impl :as impl] [uxbox.util.range-tree :as rt] - [uxbox.util.geom.snap :as snap])) + [uxbox.util.geom.snap-points :as snap])) (defonce state (l/atom {}))