mirror of
https://github.com/penpot/penpot.git
synced 2025-06-06 17:21:38 +02:00
✨ First dynamic alignment version
This commit is contained in:
parent
8cbc12ef94
commit
c5dce559cd
9 changed files with 255 additions and 135 deletions
|
@ -693,7 +693,6 @@
|
||||||
:left (gpt/point (- 1) 0)
|
:left (gpt/point (- 1) 0)
|
||||||
:right (gpt/point 1 0)))
|
:right (gpt/point 1 0)))
|
||||||
|
|
||||||
(s/def ::direction #{:up :down :right :left})
|
|
||||||
(s/def ::loc #{:up :down :bottom :top})
|
(s/def ::loc #{:up :down :bottom :top})
|
||||||
|
|
||||||
;; --- Delete Selected
|
;; --- Delete Selected
|
||||||
|
|
|
@ -148,7 +148,6 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [page (get-in state [:workspace-pages page-id])
|
(let [page (get-in state [:workspace-pages page-id])
|
||||||
objects (get-in page [:data :objects])]
|
objects (get-in page [:data :objects])]
|
||||||
(println "Update snap data")
|
|
||||||
(-> state
|
(-> state
|
||||||
(assoc :workspace-snap-data (snap/initialize-snap-data objects)))))))
|
(assoc :workspace-snap-data (snap/initialize-snap-data objects)))))))
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,18 @@
|
||||||
(defn finish-transform [state]
|
(defn finish-transform [state]
|
||||||
(update state :workspace-local dissoc :transform))
|
(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
|
;; -- RESIZE
|
||||||
(defn start-resize
|
(defn start-resize
|
||||||
[handler ids shape]
|
[handler ids shape]
|
||||||
|
@ -78,8 +90,10 @@
|
||||||
;; Vector modifiers depending on the handler
|
;; Vector modifiers depending on the handler
|
||||||
handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y))
|
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
|
;; Difference between the origin point in the coordinate system of the rotation
|
||||||
deltav (-> (snap/closest-snap snap-data resizing-shapes (gpt/to-vec initial point))
|
deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point))
|
||||||
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
(gpt/transform (gmt/rotate-matrix (- rotation)))
|
||||||
(gpt/multiply handler-modif))
|
(gpt/multiply handler-modif))
|
||||||
|
|
||||||
|
@ -124,8 +138,8 @@
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [initial (apply-zoom @ms/mouse-position)
|
(let [shape (gsh/shape->rect-shape shape)
|
||||||
shape (gsh/shape->rect-shape shape)
|
initial (handler->initial-point shape handler)
|
||||||
stoper (rx/filter ms/mouse-up? stream)
|
stoper (rx/filter ms/mouse-up? stream)
|
||||||
snap-data (get state :workspace-snap-data)
|
snap-data (get state :workspace-snap-data)
|
||||||
page-id (get state :current-page-id)
|
page-id (get state :current-page-id)
|
||||||
|
@ -184,9 +198,28 @@
|
||||||
|
|
||||||
;; -- MOVE
|
;; -- MOVE
|
||||||
|
|
||||||
|
(declare start-move)
|
||||||
|
|
||||||
(defn start-move-selected
|
(defn start-move-selected
|
||||||
[]
|
[]
|
||||||
(ptk/reify ::start-move-selected
|
(ptk/reify ::start-move-selected
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [initial (apply-zoom @ms/mouse-position)
|
||||||
|
selected (get-in state [:workspace-local :selected])
|
||||||
|
stopper (rx/filter ms/mouse-up? stream)]
|
||||||
|
(->> ms/mouse-position
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/map apply-zoom)
|
||||||
|
(rx/map #(gpt/to-vec initial %))
|
||||||
|
(rx/map #(gpt/length %))
|
||||||
|
(rx/filter #(> % 0.5))
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/map #(start-move initial selected)))))))
|
||||||
|
|
||||||
|
(defn start-move
|
||||||
|
[from-position ids]
|
||||||
|
(ptk/reify ::start-move
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(-> state
|
||||||
|
@ -194,34 +227,21 @@
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [selected (get-in state [:workspace-local :selected])
|
(let [page-id (get state :current-page-id)
|
||||||
page-id (get state :current-page-id)
|
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids)
|
||||||
shapes (mapv #(get-in state [:workspace-data page-id :objects %]) selected)
|
|
||||||
snap-data (get state :workspace-snap-data)
|
snap-data (get state :workspace-snap-data)
|
||||||
stoper (rx/filter ms/mouse-up? stream)
|
stopper (rx/filter ms/mouse-up? stream)]
|
||||||
zero-point? #(= % (gpt/point 0 0))
|
|
||||||
initial (apply-zoom @ms/mouse-position)
|
|
||||||
position @ms/mouse-position
|
|
||||||
counter (volatile! 0)]
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> ms/mouse-position
|
(->> ms/mouse-position
|
||||||
|
(rx/take-until stopper)
|
||||||
(rx/map apply-zoom)
|
(rx/map apply-zoom)
|
||||||
(rx/filter (complement zero-point?))
|
(rx/map #(gpt/to-vec from-position %))
|
||||||
(rx/map #(gpt/to-vec initial %))
|
(rx/map (snap/closest-snap-move snap-data shapes))
|
||||||
(rx/map (snap/closest-snap snap-data shapes))
|
|
||||||
(rx/map gmt/translate-matrix)
|
(rx/map gmt/translate-matrix)
|
||||||
(rx/filter #(not (gmt/base? %)))
|
(rx/map #(set-modifiers ids {:displacement %})))
|
||||||
(rx/map #(set-modifiers selected {:displacement %}))
|
|
||||||
(rx/tap #(vswap! counter inc))
|
|
||||||
(rx/take-until stoper))
|
|
||||||
(->> (rx/create (fn [sink] (sink (reduced @counter))))
|
|
||||||
(rx/mapcat (fn [n]
|
|
||||||
(if (zero? n)
|
|
||||||
(rx/empty)
|
|
||||||
(rx/of (apply-modifiers selected))))))
|
|
||||||
|
|
||||||
(rx/of finish-transform)
|
(rx/of (apply-modifiers ids)
|
||||||
)))))
|
finish-transform))))))
|
||||||
|
|
||||||
(defn- get-displacement-with-grid
|
(defn- get-displacement-with-grid
|
||||||
"Retrieve the correct displacement delta point for the
|
"Retrieve the correct displacement delta point for the
|
||||||
|
@ -240,31 +260,60 @@
|
||||||
(defn- get-displacement
|
(defn- get-displacement
|
||||||
"Retrieve the correct displacement delta point for the
|
"Retrieve the correct displacement delta point for the
|
||||||
provided direction speed and distances thresholds."
|
provided direction speed and distances thresholds."
|
||||||
[shape direction]
|
[direction]
|
||||||
(case direction
|
(case direction
|
||||||
:up (gpt/point 0 (- 1))
|
:up (gpt/point 0 (- 1))
|
||||||
:down (gpt/point 0 1)
|
:down (gpt/point 0 1)
|
||||||
:left (gpt/point (- 1) 0)
|
:left (gpt/point (- 1) 0)
|
||||||
:right (gpt/point 1 0)))
|
:right (gpt/point 1 0)))
|
||||||
|
|
||||||
|
(s/def ::direction #{:up :down :right :left})
|
||||||
|
|
||||||
(defn move-selected
|
(defn move-selected
|
||||||
[direction align?]
|
[direction align?]
|
||||||
(us/verify ::direction direction)
|
(us/verify ::direction direction)
|
||||||
(us/verify boolean? align?)
|
(us/verify boolean? align?)
|
||||||
|
|
||||||
|
(let [same-event (js/Symbol "same-event")]
|
||||||
(ptk/reify ::move-selected
|
(ptk/reify ::move-selected
|
||||||
|
IDeref
|
||||||
|
(-deref [_] direction)
|
||||||
|
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(if (nil? (get-in state [:workspace-local :current-move-selected]))
|
||||||
|
(-> state
|
||||||
|
(assoc-in [:workspace-local :transform] :move)
|
||||||
|
(assoc-in [:workspace-local :current-move-selected] same-event))
|
||||||
|
state))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [pid (:current-page-id state)
|
(if (= same-event (get-in state [:workspace-local :current-move-selected]))
|
||||||
selected (get-in state [:workspace-local :selected])
|
(let [selected (get-in state [:workspace-local :selected])
|
||||||
options (get-in state [:workspace-data pid :options])
|
move-events (->> stream
|
||||||
shapes (map #(get-in state [:workspace-data pid :objects %]) selected)
|
(rx/filter (ptk/type? ::move-selected))
|
||||||
shape (gsh/shapes->rect-shape shapes)
|
(rx/filter #(= direction (deref %))))
|
||||||
displacement (if align?
|
stopper (->> move-events
|
||||||
(get-displacement-with-grid shape direction options)
|
(rx/debounce 100)
|
||||||
(get-displacement shape direction))]
|
(rx/first))
|
||||||
(rx/of (set-modifiers selected displacement)
|
mov-vec (get-displacement direction)]
|
||||||
(apply-modifiers selected))))))
|
|
||||||
|
(rx/concat
|
||||||
|
(rx/merge
|
||||||
|
(->> move-events
|
||||||
|
(rx/take-until stopper)
|
||||||
|
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
|
||||||
|
(rx/map #(set-modifiers selected {:displacement (gmt/translate-matrix %)})))
|
||||||
|
(rx/of (move-selected direction align?)))
|
||||||
|
|
||||||
|
(rx/of (apply-modifiers selected)
|
||||||
|
(fn [state] (-> state
|
||||||
|
(update :workspace-local dissoc :current-move-selected))))
|
||||||
|
(->>
|
||||||
|
(rx/timer 100)
|
||||||
|
(rx/map (fn [] finish-transform)))))
|
||||||
|
(rx/empty))))))
|
||||||
|
|
||||||
|
|
||||||
;; -- Apply modifiers
|
;; -- Apply modifiers
|
||||||
|
|
|
@ -112,6 +112,9 @@
|
||||||
(def selected-drawing-tool
|
(def selected-drawing-tool
|
||||||
(l/derived :drawing-tool workspace-local))
|
(l/derived :drawing-tool workspace-local))
|
||||||
|
|
||||||
|
(def current-drawing-shape
|
||||||
|
(l/derived :drawing workspace-local))
|
||||||
|
|
||||||
(def selected-edition
|
(def selected-edition
|
||||||
(l/derived :edition workspace-local))
|
(l/derived :edition workspace-local))
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
[uxbox.util.geom.path :as path]
|
[uxbox.util.geom.path :as path]
|
||||||
[uxbox.util.geom.point :as gpt]
|
[uxbox.util.geom.point :as gpt]
|
||||||
[uxbox.util.i18n :as i18n :refer [t]]
|
[uxbox.util.i18n :as i18n :refer [t]]
|
||||||
|
[uxbox.util.geom.snap :as snap]
|
||||||
[uxbox.common.uuid :as uuid]))
|
[uxbox.common.uuid :as uuid]))
|
||||||
|
|
||||||
;; --- Events
|
;; --- Events
|
||||||
|
@ -115,28 +116,30 @@
|
||||||
(rx/of handle-drawing-generic)))))
|
(rx/of handle-drawing-generic)))))
|
||||||
|
|
||||||
(def handle-drawing-generic
|
(def handle-drawing-generic
|
||||||
(letfn [(initialize-drawing [state point]
|
(letfn [(initialize-drawing [state point frame-id]
|
||||||
(let [shape (get-in state [:workspace-local :drawing])
|
(let [shape (get-in state [:workspace-local :drawing])
|
||||||
shape (geom/setup shape {:x (:x point)
|
shape (geom/setup shape {:x (:x point)
|
||||||
:y (:y point)
|
:y (:y point)
|
||||||
:width 1
|
:width 1
|
||||||
:height 1})]
|
:height 1})]
|
||||||
(assoc-in state [:workspace-local :drawing] (assoc shape ::initialized? true))))
|
(assoc-in state [:workspace-local :drawing] (-> shape
|
||||||
|
(assoc :frame-id frame-id)
|
||||||
|
(assoc ::initialized? true)))))
|
||||||
|
|
||||||
(resize-shape [{:keys [x y] :as shape} initial point lock?]
|
(resize-shape [{:keys [x y] :as shape} initial snap-data point lock?]
|
||||||
(let [shape' (geom/shape->rect-shape shape)
|
(let [shape' (geom/shape->rect-shape shape)
|
||||||
shapev (gpt/point (:width shape') (:height shape'))
|
shapev (gpt/point (:width shape') (:height shape'))
|
||||||
deltav (gpt/subtract point initial)
|
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 (gpt/divide (gpt/add shapev deltav) shapev)
|
||||||
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)]
|
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)]
|
||||||
|
|
||||||
(-> shape
|
(-> shape
|
||||||
(assoc-in [:modifiers :resize-vector] scalev)
|
(assoc-in [:modifiers :resize-vector] scalev)
|
||||||
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
(assoc-in [:modifiers :resize-origin] (gpt/point x y))
|
||||||
(assoc-in [:modifiers :resize-rotation] 0))))
|
(assoc-in [:modifiers :resize-rotation] 0))))
|
||||||
|
|
||||||
(update-drawing [state initial point lock?]
|
(update-drawing [state initial snap-data point lock?]
|
||||||
(update-in state [:workspace-local :drawing] resize-shape initial point lock?))]
|
(update-in state [:workspace-local :drawing] resize-shape initial snap-data point lock?))]
|
||||||
|
|
||||||
(ptk/reify ::handle-drawing-generic
|
(ptk/reify ::handle-drawing-generic
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
|
@ -145,15 +148,29 @@
|
||||||
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
stoper? #(or (ms/mouse-up? %) (= % :interrupt))
|
||||||
stoper (rx/filter stoper? stream)
|
stoper (rx/filter stoper? stream)
|
||||||
initial @ms/mouse-position
|
initial @ms/mouse-position
|
||||||
|
snap-data (get state :workspace-snap-data)
|
||||||
mouse (->> ms/mouse-position
|
mouse (->> ms/mouse-position
|
||||||
(rx/map #(gpt/divide % (gpt/point zoom))))]
|
(rx/map #(gpt/divide % (gpt/point zoom))))
|
||||||
|
|
||||||
|
page-id (get state :current-page-id)
|
||||||
|
objects (get-in state [:workspace-data page-id :objects])
|
||||||
|
|
||||||
|
frames (->> objects
|
||||||
|
vals
|
||||||
|
(filter (comp #{:frame} :type))
|
||||||
|
(remove #(= (:id %) uuid/zero) ))
|
||||||
|
|
||||||
|
frame-id (->> frames
|
||||||
|
(filter #(geom/has-point? % initial))
|
||||||
|
first
|
||||||
|
:id)]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(->> mouse
|
(->> mouse
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map (fn [pt] #(initialize-drawing % pt))))
|
(rx/map (fn [pt] #(initialize-drawing % pt frame-id))))
|
||||||
(->> mouse
|
(->> mouse
|
||||||
(rx/with-latest vector ms/mouse-position-ctrl)
|
(rx/with-latest vector ms/mouse-position-ctrl)
|
||||||
(rx/map (fn [[pt ctrl?]] #(update-drawing % initial pt ctrl?)))
|
(rx/map (fn [[pt ctrl?]] #(update-drawing % initial snap-data pt ctrl?)))
|
||||||
(rx/take-until stoper))
|
(rx/take-until stoper))
|
||||||
(rx/of handle-finish-drawing)))))))
|
(rx/of handle-finish-drawing)))))))
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
on-resize (obj/get props "on-resize")
|
on-resize (obj/get props "on-resize")
|
||||||
on-rotate (obj/get props "on-rotate")
|
on-rotate (obj/get props "on-rotate")
|
||||||
current-transform (mf/deref refs/current-transform)
|
current-transform (mf/deref refs/current-transform)
|
||||||
|
|
||||||
{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
|
{: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)
|
radius (if (> (max width height) handler-size-threshold) 4.0 4.0)
|
||||||
|
@ -124,8 +125,7 @@
|
||||||
(for [[position [cx cy]] resize-handlers]
|
(for [[position [cx cy]] resize-handlers]
|
||||||
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
||||||
[:* {:key (name position)}
|
[:* {:key (name position)}
|
||||||
[:& rotation-handler {:key (str "rotation-" (name position))
|
[:& rotation-handler {:cx (:x tp)
|
||||||
:cx (:x tp)
|
|
||||||
:cy (:y tp)
|
:cy (:y tp)
|
||||||
:position position
|
:position position
|
||||||
:rotation (:rotation shape)
|
:rotation (:rotation shape)
|
||||||
|
@ -136,10 +136,9 @@
|
||||||
:on-click #(on-resize position %)
|
:on-click #(on-resize position %)
|
||||||
:r (/ radius zoom)
|
:r (/ radius zoom)
|
||||||
:cx (:x tp)
|
:cx (:x tp)
|
||||||
:cy (:y tp)}]]))]))
|
:cy (:y tp)}]])))]))
|
||||||
|
|
||||||
;; --- Selection Handlers (Component)
|
;; --- Selection Handlers (Component)
|
||||||
|
|
||||||
(mf/defc path-edition-selection-handlers
|
(mf/defc path-edition-selection-handlers
|
||||||
[{:keys [shape modifiers zoom] :as props}]
|
[{:keys [shape modifiers zoom] :as props}]
|
||||||
(letfn [(on-mouse-down [event index]
|
(letfn [(on-mouse-down [event index]
|
||||||
|
|
|
@ -7,13 +7,38 @@
|
||||||
|
|
||||||
(def ^:private line-color "#D383DA")
|
(def ^:private line-color "#D383DA")
|
||||||
|
|
||||||
|
(mf/defc snap-point [{:keys [point]}]
|
||||||
|
(let [{:keys [x y]} point
|
||||||
|
cross-width 3]
|
||||||
|
[: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"}}]
|
||||||
|
[:line {:x1 (- x cross-width)
|
||||||
|
:y1 (+ y cross-width)
|
||||||
|
:x2 (+ x cross-width)
|
||||||
|
:y2 (- y cross-width)
|
||||||
|
:style {:stroke line-color :stroke-width "1"}}]]))
|
||||||
|
|
||||||
|
(mf/defc snap-line [{:keys [snap point]}]
|
||||||
|
[:line {:x1 (:x snap)
|
||||||
|
:y1 (:y snap)
|
||||||
|
:x2 (:x point)
|
||||||
|
:y2 (:y point)
|
||||||
|
:style {:stroke line-color :stroke-width "1"}
|
||||||
|
:opacity 0.4}])
|
||||||
|
|
||||||
(mf/defc snap-feedback []
|
(mf/defc snap-feedback []
|
||||||
(let [selected (mf/deref refs/selected-shapes)
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
shapes (mf/deref (refs/objects-by-id selected))
|
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)
|
filter-shapes (mf/deref refs/selected-shapes-with-children)
|
||||||
current-transform (mf/deref refs/current-transform)
|
current-transform (mf/deref refs/current-transform)
|
||||||
snap-data (mf/deref refs/workspace-snap-data)]
|
snap-data (mf/deref refs/workspace-snap-data)
|
||||||
(when (not (nil? current-transform))
|
shapes (if drawing [drawing] selected-shapes)]
|
||||||
|
(when (or drawing current-transform)
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
(for [point (snap/shape-snap-points shape)]
|
(for [point (snap/shape-snap-points shape)]
|
||||||
(let [frame-id (:frame-id shape)
|
(let [frame-id (:frame-id shape)
|
||||||
|
@ -24,24 +49,14 @@
|
||||||
(snap/get-snap-points snap-data frame-id filter-shapes point :y)))]
|
(snap/get-snap-points snap-data frame-id filter-shapes point :y)))]
|
||||||
(if (not-empty snaps)
|
(if (not-empty snaps)
|
||||||
[:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))}
|
[:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))}
|
||||||
[:circle {:cx (:x point)
|
[:& snap-point {:point point}]
|
||||||
:cy (:y point)
|
|
||||||
:r 2
|
|
||||||
:fill line-color}]
|
|
||||||
|
|
||||||
(for [snap snaps]
|
(for [snap snaps]
|
||||||
[:circle {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
[:& snap-point {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||||
:cx (:x snap)
|
:point snap}])
|
||||||
:cy (:y snap)
|
|
||||||
:r 2
|
|
||||||
:fill line-color}])
|
|
||||||
|
|
||||||
(for [snap snaps]
|
(for [snap snaps]
|
||||||
[:line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
[:& snap-line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap))
|
||||||
:x1 (:x snap)
|
:snap snap
|
||||||
:y1 (:y snap)
|
:point point}])])))))))
|
||||||
:x2 (:x point)
|
|
||||||
:y2 (:y point)
|
|
||||||
:style {:stroke line-color :stroke-width "1"}
|
|
||||||
:opacity 0.4}])])))))))
|
|
||||||
|
|
||||||
|
|
|
@ -306,13 +306,14 @@
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:edition edition}])
|
:edition edition}])
|
||||||
|
|
||||||
[:& snap-feedback]
|
|
||||||
|
|
||||||
(when-let [drawing-shape (:drawing local)]
|
(when-let [drawing-shape (:drawing local)]
|
||||||
[:& draw-area {:shape drawing-shape
|
[:& draw-area {:shape drawing-shape
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:modifiers (:modifiers local)}])
|
:modifiers (:modifiers local)}])
|
||||||
|
|
||||||
|
[:& snap-feedback]
|
||||||
|
|
||||||
(if (contains? flags :grid)
|
(if (contains? flags :grid)
|
||||||
[:& grid])]
|
[:& grid])]
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
(ns uxbox.util.geom.snap
|
(ns uxbox.util.geom.snap
|
||||||
(:require
|
(:require
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
|
[clojure.set :as set]
|
||||||
[uxbox.util.math :as mth]
|
[uxbox.util.math :as mth]
|
||||||
[uxbox.common.uuid :refer [zero]]
|
[uxbox.common.uuid :refer [zero]]
|
||||||
[uxbox.util.geom.shapes :as gsh]
|
[uxbox.util.geom.shapes :as gsh]
|
||||||
[uxbox.util.geom.point :as gpt]
|
[uxbox.util.geom.point :as gpt]))
|
||||||
[uxbox.util.debug :refer [logjs]]))
|
|
||||||
|
|
||||||
(def ^:private snap-accuracy 8)
|
(def ^:private snap-accuracy 20)
|
||||||
|
|
||||||
(defn mapm
|
(defn mapm
|
||||||
"Map over the values of a map"
|
"Map over the values of a map"
|
||||||
|
@ -33,12 +33,44 @@
|
||||||
(gpt/point x (+ y height))
|
(gpt/point x (+ y height))
|
||||||
(gpt/point x (+ y (/ height 2)))})
|
(gpt/point x (+ y (/ height 2)))})
|
||||||
|
|
||||||
(defn shape-snap-points [shape]
|
(defn- frame-snap-points-resize [{:keys [x y width height]} handler]
|
||||||
(if (= :frame (:type shape))
|
(case handler
|
||||||
(frame-snap-points shape)
|
: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-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)
|
(let [modified-path (gsh/transform-apply-modifiers shape)
|
||||||
shape-center (gsh/center modified-path)]
|
shape-center (gsh/center modified-path)]
|
||||||
(into #{shape-center} (:segments modified-path)))))
|
(case (:type shape)
|
||||||
|
:frame (frame-snap-points shape)
|
||||||
|
(:path :curve) (into #{shape-center} (-> modified-path gsh/shape->rect-shape :segments))
|
||||||
|
(into #{shape-center} (-> modified-path :segments)))))
|
||||||
|
|
||||||
(defn create-coord-data [shapes coord]
|
(defn create-coord-data [shapes coord]
|
||||||
(let [process-shape
|
(let [process-shape
|
||||||
|
@ -62,10 +94,9 @@
|
||||||
(filter #(= :frame (:type %)))
|
(filter #(= :frame (:type %)))
|
||||||
(remove #(= zero (:id %)))
|
(remove #(= zero (:id %)))
|
||||||
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
(reduce #(update %1 (:id %2) conj %2) frame-shapes))]
|
||||||
(logjs "snap-data"
|
|
||||||
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
(mapm (fn [shapes] {:x (create-coord-data shapes :x)
|
||||||
:y (create-coord-data shapes :y)})
|
:y (create-coord-data shapes :y)})
|
||||||
frame-shapes))))
|
frame-shapes)))
|
||||||
|
|
||||||
(defn range-query
|
(defn range-query
|
||||||
"Queries the snap-data within a range of values"
|
"Queries the snap-data within a range of values"
|
||||||
|
@ -117,22 +148,14 @@
|
||||||
;; Otherwise the root frame is the common
|
;; Otherwise the root frame is the common
|
||||||
:else zero)))
|
:else zero)))
|
||||||
|
|
||||||
(defn closest-snap
|
(defn- closest-snap
|
||||||
([snap-data shapes] (partial closest-snap snap-data shapes))
|
[snap-data shapes trans-vec shapes-points]
|
||||||
([snap-data shapes trans-vec]
|
|
||||||
(let [;; Get the common frame-id to make the snap
|
(let [;; Get the common frame-id to make the snap
|
||||||
frame-id (snap-frame-id shapes)
|
frame-id (snap-frame-id shapes)
|
||||||
|
|
||||||
;; We don't want to snap to the shapes currently moving
|
;; We don't want to snap to the shapes currently transformed
|
||||||
remove-shapes (into #{} (map :id shapes))
|
remove-shapes (into #{} (map :id shapes))
|
||||||
|
|
||||||
shapes-points (->> shapes
|
|
||||||
;; Unroll all the possible snap-points
|
|
||||||
(mapcat shape-snap-points)
|
|
||||||
|
|
||||||
;; Move the points in the translation vector
|
|
||||||
(map #(gpt/add % trans-vec)))
|
|
||||||
|
|
||||||
;; The snap is a tuple. The from is the point in the current moving shape
|
;; 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
|
;; 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 --> snap-to and move the position in that vector
|
||||||
|
@ -142,14 +165,29 @@
|
||||||
snapv (gpt/to-vec (gpt/point snap-from-x snap-from-y)
|
snapv (gpt/to-vec (gpt/point snap-from-x snap-from-y)
|
||||||
(gpt/point snap-to-x snap-to-y))]
|
(gpt/point snap-to-x snap-to-y))]
|
||||||
|
|
||||||
(gpt/add trans-vec snapv))))
|
(gpt/add trans-vec snapv)))
|
||||||
|
|
||||||
|
(defn closest-snap-point
|
||||||
|
[snap-data shapes point]
|
||||||
|
(closest-snap snap-data shapes point [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))
|
||||||
|
|
||||||
|
;; 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]
|
(defn get-snap-points [snap-data frame-id filter-shapes point coord]
|
||||||
(let [value (coord point)
|
(let [value (coord point)
|
||||||
|
|
||||||
;; Search for values within 1 pixel
|
;; Search for values within 1 pixel
|
||||||
snap-matches (-> (get-in snap-data [frame-id coord])
|
snap-matches (-> (get-in snap-data [frame-id coord])
|
||||||
(range-query (- value 0.5) (+ value 0.5))
|
(range-query (- value 1) (+ value 1))
|
||||||
(remove-from-snap-points filter-shapes))
|
(remove-from-snap-points filter-shapes))
|
||||||
|
|
||||||
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue