mirror of
https://github.com/penpot/penpot.git
synced 2025-02-08 16:18:11 -05:00
♻️ Moved snap calculations to web-worker
This commit is contained in:
parent
c73a2014c3
commit
80a86b14b8
14 changed files with 291 additions and 214 deletions
|
@ -10,7 +10,7 @@
|
|||
|
||||
danlentz/clj-uuid {:mvn/version "0.1.9"}
|
||||
|
||||
funcool/beicon {:mvn/version "2020.03.29-1"}
|
||||
funcool/beicon {:mvn/version "2020.05.08-2"}
|
||||
funcool/cuerdas {:mvn/version "2020.03.26-3"}
|
||||
funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"}
|
||||
|
||||
|
|
|
@ -155,8 +155,7 @@
|
|||
(-> state
|
||||
(assoc :current-page-id page-id ; mainly used by events
|
||||
:workspace-local local
|
||||
:workspace-page (dissoc page :data)
|
||||
:workspace-snap-data (snap/initialize-snap-data (get-in page [:data :objects])))
|
||||
:workspace-page (dissoc page :data))
|
||||
(assoc-in [:workspace-data page-id] (:data page)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -29,8 +29,7 @@
|
|||
(get-ids [this]))
|
||||
|
||||
(declare setup-selection-index)
|
||||
(declare update-selection-index)
|
||||
(declare update-snap-data)
|
||||
(declare update-page-indices)
|
||||
(declare reset-undo)
|
||||
(declare append-undo)
|
||||
|
||||
|
@ -63,8 +62,7 @@
|
|||
(let [page (:workspace-page state)
|
||||
uidx (get-in state [:workspace-local :undo-index] ::not-found)]
|
||||
(rx/concat
|
||||
(rx/of (update-selection-index (:id page))
|
||||
(update-snap-data (:id page)))
|
||||
(rx/of (update-page-indices (:id page)))
|
||||
|
||||
(when (and save-undo? (not= uidx ::not-found))
|
||||
(rx/of (reset-undo uidx)))
|
||||
|
@ -146,34 +144,24 @@
|
|||
(ptk/reify ::setup-selection-index
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [msg {:cmd :selection/create-index
|
||||
(let [msg {:cmd :create-page-indices
|
||||
:file-id (:id file)
|
||||
:pages pages}]
|
||||
(->> (uw/ask! msg)
|
||||
(rx/map (constantly ::index-initialized)))))))
|
||||
|
||||
|
||||
(defn update-selection-index
|
||||
(defn update-page-indices
|
||||
[page-id]
|
||||
(ptk/reify ::update-selection-index
|
||||
(ptk/reify ::update-page-indices
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [objects (get-in state [:workspace-pages page-id :data :objects])
|
||||
lookup #(get objects %)]
|
||||
(uw/ask! {:cmd :selection/update-index
|
||||
(uw/ask! {:cmd :update-page-indices
|
||||
:page-id page-id
|
||||
:objects objects})))))
|
||||
|
||||
(defn update-snap-data
|
||||
[page-id]
|
||||
(ptk/reify ::update-snap-data
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page (get-in state [:workspace-pages page-id])
|
||||
objects (get-in page [:data :objects])]
|
||||
(-> state
|
||||
(assoc :workspace-snap-data (snap/initialize-snap-data objects)))))))
|
||||
|
||||
;; --- Common Helpers & Events
|
||||
|
||||
(defn- calculate-frame-overlap
|
||||
|
|
|
@ -79,15 +79,13 @@
|
|||
;; -- RESIZE
|
||||
(defn start-resize
|
||||
[handler ids shape]
|
||||
(letfn [(resize [shape initial resizing-shapes snap-data [point lock?]]
|
||||
(letfn [(resize [shape initial resizing-shapes [point lock? point-snap]]
|
||||
(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))
|
||||
|
||||
point-snap (snap/closest-snap-point snap-data resizing-shapes point)
|
||||
|
||||
;; Difference between the origin point in the coordinate system of the rotation
|
||||
deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point))
|
||||
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
||||
|
@ -137,7 +135,6 @@
|
|||
(let [shape (gsh/shape->rect-shape shape)
|
||||
initial (handler->initial-point shape handler)
|
||||
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
|
||||
|
@ -145,7 +142,10 @@
|
|||
;; (rx/mapcat apply-grid-alignment)
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/mapcat (partial resize shape initial resizing-shapes snap-data))
|
||||
(rx/switch-map (fn [[point :as current]]
|
||||
(->> (snap/closest-snap-point page-id resizing-shapes point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/mapcat (partial resize shape initial resizing-shapes))
|
||||
(rx/take-until stoper))
|
||||
#_(rx/empty)
|
||||
(rx/of (apply-modifiers ids)
|
||||
|
@ -222,13 +222,12 @@
|
|||
(watch [_ state stream]
|
||||
(let [page-id (get state :current-page-id)
|
||||
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids)
|
||||
snap-data (get state :workspace-snap-data)
|
||||
stopper (rx/filter ms/mouse-up? stream)]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/take-until stopper)
|
||||
(rx/map #(gpt/to-vec from-position %))
|
||||
(rx/map (snap/closest-snap-move snap-data shapes))
|
||||
(rx/switch-map #(snap/closest-snap-move page-id shapes %))
|
||||
(rx/map gmt/translate-matrix)
|
||||
(rx/map #(set-modifiers ids {:displacement %})))
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
(def workspace-page
|
||||
(l/derived :workspace-page st/state))
|
||||
|
||||
(def workspace-page-id
|
||||
(l/derived :id workspace-page))
|
||||
|
||||
(def workspace-file
|
||||
(l/derived :workspace-file st/state))
|
||||
|
||||
|
|
|
@ -116,20 +116,9 @@
|
|||
(rx/of handle-drawing-generic)))))
|
||||
|
||||
(def handle-drawing-generic
|
||||
(letfn [(initialize-drawing [state point frame-id]
|
||||
(let [shape (get-in state [:workspace-local :drawing])
|
||||
shape (geom/setup shape {:x (:x point)
|
||||
:y (:y point)
|
||||
:width 1
|
||||
:height 1})]
|
||||
(assoc-in state [:workspace-local :drawing] (-> shape
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc ::initialized? true)))))
|
||||
|
||||
(resize-shape [{:keys [x y] :as shape} initial snap-data point lock?]
|
||||
(letfn [(resize-shape [{:keys [x y] :as shape} initial point lock? point-snap]
|
||||
(let [shape' (geom/shape->rect-shape shape)
|
||||
shapev (gpt/point (:width shape') (:height shape'))
|
||||
point-snap (snap/closest-snap-point snap-data [shape] point)
|
||||
deltav (gpt/to-vec initial point-snap)
|
||||
scalev (gpt/divide (gpt/add shapev deltav) shapev)
|
||||
scalev (if lock?
|
||||
|
@ -141,8 +130,8 @@
|
|||
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
||||
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||
|
||||
(update-drawing [state initial snap-data point lock?]
|
||||
(update-in state [:workspace-local :drawing] resize-shape initial snap-data point lock?))]
|
||||
(update-drawing [state initial point lock? point-snap]
|
||||
(update-in state [:workspace-local :drawing] resize-shape initial point lock? point-snap))]
|
||||
|
||||
(ptk/reify ::handle-drawing-generic
|
||||
ptk/WatchEvent
|
||||
|
@ -151,7 +140,6 @@
|
|||
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
||||
stoper (rx/filter stoper? stream)
|
||||
initial @ms/mouse-position
|
||||
snap-data (get state :workspace-snap-data)
|
||||
mouse ms/mouse-position
|
||||
|
||||
page-id (get state :current-page-id)
|
||||
|
@ -162,17 +150,26 @@
|
|||
(filter (comp #{:frame} :type))
|
||||
(remove #(= (:id %) uuid/zero) ))
|
||||
|
||||
frame-id (->> frames
|
||||
(filter #(geom/has-point? % initial))
|
||||
first
|
||||
:id)]
|
||||
frame-id (or (->> frames
|
||||
(filter #(geom/has-point? % initial))
|
||||
first
|
||||
:id)
|
||||
uuid/zero)
|
||||
|
||||
shape (-> state
|
||||
(get-in [:workspace-local :drawing])
|
||||
(geom/setup {:x (:x initial) :y (:y initial) :width 1 :height 1})
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc ::initialized? true))]
|
||||
(rx/concat
|
||||
(->> mouse
|
||||
(rx/take 1)
|
||||
(rx/map (fn [pt] #(initialize-drawing % pt (or frame-id uuid/zero)))))
|
||||
(rx/of #(assoc-in state [:workspace-local :drawing] shape))
|
||||
|
||||
(->> mouse
|
||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % initial snap-data pt ctrl?)))
|
||||
(rx/switch-map (fn [[point :as current]]
|
||||
(->> (snap/closest-snap-point page-id [shape] point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/map (fn [[pt ctrl? point-snap]] #(update-drawing % initial pt ctrl? point-snap)))
|
||||
(rx/take-until stoper))
|
||||
(rx/of handle-finish-drawing)))))))
|
||||
|
||||
|
|
|
@ -48,28 +48,4 @@
|
|||
:fill "transparent"
|
||||
:stroke-width "1px"
|
||||
:stroke-opacity 0.5
|
||||
:pointer-events "none"}}]
|
||||
|
||||
#_(for [point (snap/shape-snap-points shape)]
|
||||
(let [point (gpt/subtract point (gpt/point (:x frame) (:y frame)))]
|
||||
[:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))}
|
||||
[:circle {:cx (:x point)
|
||||
:cy (:y point)
|
||||
:r 4
|
||||
:fill line-color}]
|
||||
|
||||
[:line {:x1 (:x point)
|
||||
:y1 -10000
|
||||
:x2 (:x point)
|
||||
:y2 10000
|
||||
:style {:stroke line-color :stroke-width "1"}
|
||||
:opacity 0.4}]
|
||||
|
||||
[:line {:x1 -10000
|
||||
:y1 (:y point)
|
||||
:x2 10000
|
||||
:y2 (:y point)
|
||||
:style {:stroke line-color :stroke-width "1"}
|
||||
:opacity 0.4}]]
|
||||
))
|
||||
])))
|
||||
:pointer-events "none"}}]])))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns uxbox.main.ui.workspace.snap-feedback
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.util.geom.snap :as snap]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
@ -30,8 +31,54 @@
|
|||
:style {:stroke line-color :stroke-width "1"}
|
||||
:opacity 0.4}])
|
||||
|
||||
(mf/defc snap-feedback []
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
(defn get-snap [coord {:keys [shapes page-id filter-shapes]}]
|
||||
(->> (rx/from shapes)
|
||||
(rx/flat-map (fn [shape]
|
||||
(->> (snap/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)
|
||||
(rx/map #(vector point % coord)))))
|
||||
(rx/reduce conj [])))
|
||||
|
||||
(mf/defc snap-feedback-points
|
||||
[{:keys [shapes page-id filter-shapes] :as props}]
|
||||
(let [state (mf/use-state [])
|
||||
subject (mf/use-memo #(rx/subject))]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(->> subject
|
||||
(rx/switch-map #(rx/combine-latest
|
||||
concat
|
||||
(get-snap :y %)
|
||||
(get-snap :x %)))
|
||||
(rx/subs #(reset! state %)))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shapes)
|
||||
(fn []
|
||||
(rx/push! subject props)))
|
||||
|
||||
[:g.snap-feedback
|
||||
(for [[point snaps coord] @state]
|
||||
(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}]
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-point {:key (str "snap-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord))
|
||||
:point snap}])
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-line {:key (str "line-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord))
|
||||
:snap snap
|
||||
:point point}])]))]))
|
||||
|
||||
(mf/defc snap-feedback [{:keys []}]
|
||||
(let [page-id (mf/deref refs/workspace-page-id)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected-shapes (mf/deref (refs/objects-by-id selected))
|
||||
drawing (mf/deref refs/current-drawing-shape)
|
||||
filter-shapes (mf/deref refs/selected-shapes-with-children)
|
||||
|
@ -39,24 +86,7 @@
|
|||
snap-data (mf/deref refs/workspace-snap-data)
|
||||
shapes (if drawing [drawing] selected-shapes)]
|
||||
(when (or drawing 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))}
|
||||
[:& snap-point {:point point}]
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-point {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||
:point snap}])
|
||||
|
||||
(for [snap snaps]
|
||||
[:& snap-line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||
:snap snap
|
||||
:point point}])])))))))
|
||||
[:& snap-feedback-points {:shapes shapes
|
||||
:page-id page-id
|
||||
:filter-shapes filter-shapes}])))
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
(defn on-error
|
||||
[instance error]
|
||||
(js/console.log "error on worker" error))
|
||||
(js/console.error "Error on worker" (.-data error)))
|
||||
|
||||
(defonce instance
|
||||
(when (not= *target* "nodejs")
|
||||
|
|
|
@ -11,19 +11,15 @@
|
|||
(: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.geom.point :as gpt]
|
||||
[uxbox.util.range-tree :as rt]))
|
||||
[uxbox.main.worker :as uw]))
|
||||
|
||||
(def ^:private snap-accuracy 10)
|
||||
|
||||
(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)
|
||||
|
@ -55,15 +51,6 @@
|
|||
:bottom-left 3
|
||||
:left 3})
|
||||
|
||||
(defn shape-snap-points-resize
|
||||
[handler shape]
|
||||
(let [modified-path (gsh/transform-apply-modifiers shape)
|
||||
point-idx (handler->point-idx handler)]
|
||||
#{(case (:type shape)
|
||||
:frame (frame-snap-points-resize shape handler)
|
||||
(:path :curve) (-> modified-path gsh/shape->rect-shape :segments (nth point-idx))
|
||||
(-> modified-path :segments (nth point-idx)))}))
|
||||
|
||||
(defn shape-snap-points
|
||||
[shape]
|
||||
(let [modified-path (gsh/transform-apply-modifiers shape)
|
||||
|
@ -73,63 +60,23 @@
|
|||
(:path :curve) (into #{shape-center} (-> modified-path gsh/shape->rect-shape :segments))
|
||||
(into #{shape-center} (-> modified-path :segments)))))
|
||||
|
||||
(defn create-coord-data [shapes coord]
|
||||
(let [process-shape (fn [coord]
|
||||
(fn [shape]
|
||||
(let [points (shape-snap-points shape)]
|
||||
(map #(vector % (:id shape)) points))))
|
||||
into-tree (fn [tree [point _ :as data]]
|
||||
(rt/insert tree (coord point) data))]
|
||||
(->> shapes
|
||||
(mapcat (process-shape coord))
|
||||
(reduce into-tree (rt/make-tree)))))
|
||||
|
||||
(defn initialize-snap-data
|
||||
"Initialize the snap information with the current workspace information"
|
||||
[objects]
|
||||
(let [shapes (vals objects)
|
||||
frame-shapes (->> shapes
|
||||
(filter (comp not nil? :frame-id))
|
||||
(group-by :frame-id))
|
||||
(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)))))))
|
||||
|
||||
frame-shapes (->> shapes
|
||||
(filter #(= :frame (:type %)))
|
||||
(remove #(= zero (:id %)))
|
||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
||||
:y (create-coord-data shapes :y)})
|
||||
frame-shapes)))
|
||||
(defn- calculate-distance [query-result point coord]
|
||||
(->> query-result
|
||||
(map (fn [[value data]] [(mth/abs (- value (coord point))) [(coord point) value]]))))
|
||||
|
||||
(defn remove-from-snap-points [snap-points ids-to-remove]
|
||||
(->> snap-points
|
||||
(map (fn [[value data]] [value (remove (comp ids-to-remove second) data)]))
|
||||
(filter (fn [[_ data]] (not (empty? data))))))
|
||||
|
||||
(defn search-snap-point
|
||||
"Search snap for a single point in the `coord` given"
|
||||
[point coord snap-data filter-shapes]
|
||||
|
||||
(let [coord-value (get point coord)
|
||||
|
||||
;; This gives a list of [value [[point1 uuid1] [point2 uuid2] ...] we need to remove
|
||||
;; the shapes in filter shapes
|
||||
candidates (-> snap-data
|
||||
(rt/range-query (- coord-value snap-accuracy) (+ coord-value snap-accuracy))
|
||||
(remove-from-snap-points filter-shapes))
|
||||
|
||||
;; Now return with the distance and the from-to pair that we'll return if this is the chosen
|
||||
point-snaps (map (fn [[cand-value data]] [(mth/abs (- coord-value cand-value)) [coord-value cand-value]]) candidates)]
|
||||
point-snaps))
|
||||
|
||||
(defn search-snap
|
||||
"Search a snap point in one axis `snap-data` contains the information to make the snap.
|
||||
`points` are the points that we need to search for a snap and `filter-shapes` is a set of uuids
|
||||
containgin the shapes that should be ignored to get a snap (usually because they are being moved)"
|
||||
[points coord snap-data filter-shapes]
|
||||
|
||||
(let [snap-points (mapcat #(search-snap-point % coord snap-data filter-shapes) points)
|
||||
result (->> snap-points (apply min-key first) second)]
|
||||
result))
|
||||
(defn- get-min-distance-snap [points coord]
|
||||
(fn [query-result]
|
||||
(->> points
|
||||
(mapcat #(calculate-distance query-result % coord))
|
||||
(apply min-key first)
|
||||
second)))
|
||||
|
||||
(defn snap-frame-id [shapes]
|
||||
(let [frames (into #{} (map :frame-id shapes))]
|
||||
|
@ -143,53 +90,63 @@
|
|||
;; 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
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:coord coord
|
||||
:ranges [[(- value 1) (+ value 1)]]})
|
||||
(rx/first)
|
||||
(rx/map (remove-from-snap-points filter-shapes))
|
||||
(rx/map flatten-to-points))))
|
||||
|
||||
(defn- search-snap
|
||||
[page-id frame-id points coord filter-shapes]
|
||||
(let [ranges (->> points
|
||||
(map coord)
|
||||
(mapv #(vector (- % snap-accuracy)
|
||||
(+ % snap-accuracy))))]
|
||||
(->> (uw/ask! {:cmd :snaps/range-query
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:coord coord
|
||||
:ranges ranges})
|
||||
(rx/first)
|
||||
(rx/map (remove-from-snap-points filter-shapes))
|
||||
(rx/map (get-min-distance-snap points coord)))))
|
||||
|
||||
(defn- closest-snap
|
||||
[snap-data shapes trans-vec shapes-points]
|
||||
(let [;; Get the common frame-id to make the snap
|
||||
frame-id (snap-frame-id shapes)
|
||||
|
||||
;; We don't want to snap to the shapes currently transformed
|
||||
remove-shapes (into #{} (map :id shapes))
|
||||
|
||||
;; The snap is a tuple. The from is the point in the current moving shape
|
||||
;; the "to" is the point where we'll snap. So we need to create a vector
|
||||
;; snap-from --> snap-to and move the position in that vector
|
||||
[snap-from-x snap-to-x] (search-snap shapes-points :x (get-in snap-data [frame-id :x]) remove-shapes)
|
||||
[snap-from-y snap-to-y] (search-snap shapes-points :y (get-in snap-data [frame-id :y]) remove-shapes)
|
||||
|
||||
snapv (gpt/to-vec (gpt/point (or snap-from-x 0) (or snap-from-y 0))
|
||||
(gpt/point (or snap-to-x 0) (or snap-to-y 0)))]
|
||||
|
||||
(gpt/add trans-vec snapv)))
|
||||
[page-id frame-id points filter-shapes]
|
||||
(let [snap-x (search-snap page-id frame-id points :x filter-shapes)
|
||||
snap-y (search-snap page-id frame-id points :y filter-shapes)
|
||||
snap-as-vector (fn [[from-x to-x] [from-y to-y]]
|
||||
(let [from (gpt/point (or from-x 0) (or from-y 0))
|
||||
to (gpt/point (or to-x 0) (or to-y 0))]
|
||||
(gpt/to-vec from to)))]
|
||||
;; snap-x is the second parameter because is the "source" to combine
|
||||
(rx/combine-latest snap-as-vector snap-y snap-x)))
|
||||
|
||||
(defn closest-snap-point
|
||||
[snap-data shapes point]
|
||||
(closest-snap snap-data shapes point [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 %)))))
|
||||
|
||||
(defn closest-snap-move
|
||||
([snap-data shapes] (partial closest-snap-move snap-data shapes))
|
||||
([snap-data shapes trans-vec]
|
||||
(let [shapes-points (->> shapes
|
||||
;; Unroll all the possible snap-points
|
||||
(mapcat (partial shape-snap-points))
|
||||
[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))
|
||||
|
||||
;; Move the points in the translation vector
|
||||
(map #(gpt/add % trans-vec)))]
|
||||
(closest-snap snap-data shapes trans-vec shapes-points))))
|
||||
|
||||
(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])
|
||||
(rt/range-query (- value 1) (+ value 1))
|
||||
(remove-from-snap-points filter-shapes))
|
||||
|
||||
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
||||
snap-points))
|
||||
|
||||
(defn is-snapping? [snap-data frame-id shape-id point coord]
|
||||
(let [value (coord point)
|
||||
;; Search for values within 1 pixel
|
||||
snap-points (rt/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)))
|
||||
;; 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 %)))))
|
||||
|
|
|
@ -103,6 +103,12 @@ goog.scope(function() {
|
|||
recToString(this.root, result);
|
||||
return result.join(", ");
|
||||
}
|
||||
|
||||
asMap() {
|
||||
const result = {};
|
||||
recTreeAsMap(this.root, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Tree implementation functions
|
||||
|
@ -243,7 +249,6 @@ goog.scope(function() {
|
|||
recRangeQuery(branch.left, fromValue, toValue, result);
|
||||
}
|
||||
if (fromValue <= branch.value && toValue >= branch.value) {
|
||||
// Array.prototype.push.apply(result, branch.data);
|
||||
result.push(vec([branch.value, vec(branch.data)]))
|
||||
}
|
||||
if (toValue > branch.value) {
|
||||
|
@ -345,6 +350,16 @@ goog.scope(function() {
|
|||
return "[" + printTree(tree.left) + " " + val + " " + printTree(tree.right) + "]";
|
||||
}
|
||||
|
||||
function recTreeAsMap(branch, result) {
|
||||
if (!branch) {
|
||||
return result;
|
||||
}
|
||||
recTreeAsMap(branch.left, result);
|
||||
result[branch.value] = branch.data;
|
||||
recTreeAsMap(branch.right, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// External API to CLJS
|
||||
const self = uxbox.util.range_tree;
|
||||
self.make_tree = () => new RangeTree();
|
||||
|
@ -367,5 +382,6 @@ goog.scope(function() {
|
|||
self.empty_QMARK_ = (tree) => tree.isEmpty();
|
||||
self.height = (tree) => tree.height();
|
||||
self.print = (tree) => printTree(tree.root);
|
||||
self.as_map = (tree) => tree.asMap();
|
||||
});
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[uxbox.worker.impl :as impl]
|
||||
[uxbox.worker.selection]
|
||||
[uxbox.worker.thumbnails]
|
||||
[uxbox.worker.snaps]
|
||||
[uxbox.util.transit :as t]
|
||||
[uxbox.util.worker :as w]))
|
||||
|
||||
|
|
|
@ -20,3 +20,17 @@
|
|||
(defmethod handler :echo
|
||||
[message]
|
||||
message)
|
||||
|
||||
(defmethod handler :create-page-indices
|
||||
[message]
|
||||
(handler (-> message
|
||||
(assoc :cmd :selection/create-index)))
|
||||
(handler (-> message
|
||||
(assoc :cmd :snaps/create-index))))
|
||||
|
||||
(defmethod handler :update-page-indices
|
||||
[message]
|
||||
(handler (-> message
|
||||
(assoc :cmd :selection/update-index)))
|
||||
(handler (-> message
|
||||
(assoc :cmd :snaps/update-index))))
|
||||
|
|
97
frontend/src/uxbox/worker/snaps.cljs
Normal file
97
frontend/src/uxbox/worker/snaps.cljs
Normal file
|
@ -0,0 +1,97 @@
|
|||
;; 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.worker.snaps
|
||||
(:require
|
||||
[okulary.core :as l]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.worker.impl :as impl]
|
||||
[uxbox.util.range-tree :as rt]
|
||||
[uxbox.util.geom.snap :as snap]))
|
||||
|
||||
(defonce state (l/atom {}))
|
||||
|
||||
(defn- create-coord-data
|
||||
"Initializes the range tree given the shapes"
|
||||
[shapes coord]
|
||||
(let [process-shape (fn [coord]
|
||||
(fn [shape]
|
||||
(let [points (snap/shape-snap-points shape)]
|
||||
(map #(vector % (:id shape)) points))))
|
||||
into-tree (fn [tree [point _ :as data]]
|
||||
(rt/insert tree (coord point) data))]
|
||||
(->> shapes
|
||||
(mapcat (process-shape coord))
|
||||
(reduce into-tree (rt/make-tree)))))
|
||||
|
||||
(defn- mapm
|
||||
"Map over the values of a map"
|
||||
[mfn coll]
|
||||
(into {} (map (fn [[key val]] [key (mfn val)]) coll)))
|
||||
|
||||
(defn- initialize-snap-data
|
||||
"Initialize the snap information with the current workspace information"
|
||||
[objects]
|
||||
(let [shapes (vals objects)
|
||||
frame-shapes (->> shapes
|
||||
(filter (comp not nil? :frame-id))
|
||||
(group-by :frame-id))
|
||||
|
||||
frame-shapes (->> shapes
|
||||
(filter #(= :frame (:type %)))
|
||||
(remove #(= uuid/zero (:id %)))
|
||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
||||
:y (create-coord-data shapes :y)})
|
||||
frame-shapes)))
|
||||
|
||||
(defn- log-state
|
||||
"Helper function to print a friendly version of the snap tree. Debugging purposes"
|
||||
[]
|
||||
(let [process-frame-data #(mapm rt/as-map %)
|
||||
process-page-data #(mapm process-frame-data %)]
|
||||
(js/console.log "STATE" (clj->js (mapm process-page-data @state)))))
|
||||
|
||||
(defn- index-page [state page-id objects]
|
||||
(let [snap-data (initialize-snap-data objects)]
|
||||
(assoc state page-id snap-data)))
|
||||
|
||||
;; Public API
|
||||
(defmethod impl/handler :snaps/create-index
|
||||
[{:keys [file-id pages] :as message}]
|
||||
|
||||
;; Create the index
|
||||
(letfn [(process-page [state page]
|
||||
(let [id (:id page)
|
||||
objects (get-in page [:data :objects])]
|
||||
(index-page state id objects)))]
|
||||
(swap! state #(reduce process-page % pages)))
|
||||
|
||||
#_(log-state)
|
||||
;; Return nil so the worker will not answer anything back
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :snaps/update-index
|
||||
[{:keys [page-id objects] :as message}]
|
||||
;; TODO: Check the difference and update the index acordingly
|
||||
(swap! state index-page page-id objects)
|
||||
#_(log-state)
|
||||
nil)
|
||||
|
||||
(defmethod impl/handler :snaps/range-query
|
||||
[{:keys [page-id frame-id coord ranges] :as message}]
|
||||
(letfn [(calculate-range [[from to]]
|
||||
(-> @state
|
||||
(get-in [page-id frame-id coord])
|
||||
(rt/range-query from to)))]
|
||||
(->> ranges
|
||||
(mapcat calculate-range)
|
||||
set ;; unique
|
||||
(into []))))
|
||||
|
Loading…
Add table
Reference in a new issue