mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 15:36:11 +02:00
✨ Snap for moving path nodes and handlers
This commit is contained in:
parent
de8207c5a6
commit
f396ef4fa0
7 changed files with 83 additions and 75 deletions
|
@ -24,8 +24,8 @@
|
||||||
(d/export edition/start-path-edit)
|
(d/export edition/start-path-edit)
|
||||||
|
|
||||||
;; Selection
|
;; Selection
|
||||||
(d/export selection/select-handler)
|
|
||||||
(d/export selection/handle-selection)
|
(d/export selection/handle-selection)
|
||||||
|
(d/export selection/select-node)
|
||||||
(d/export selection/path-handler-enter)
|
(d/export selection/path-handler-enter)
|
||||||
(d/export selection/path-handler-leave)
|
(d/export selection/path-handler-leave)
|
||||||
(d/export selection/path-pointer-enter)
|
(d/export selection/path-pointer-enter)
|
||||||
|
|
|
@ -35,17 +35,20 @@
|
||||||
:x dx :y dy :c2x dx :c2y dy))))))
|
:x dx :y dy :c2x dx :c2y dy))))))
|
||||||
|
|
||||||
(defn modify-handler [id index prefix dx dy match-opposite?]
|
(defn modify-handler [id index prefix dx dy match-opposite?]
|
||||||
(ptk/reify ::modify-point
|
(ptk/reify ::modify-handler
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [content (get-in state (st/get-path state :content))
|
(let [content (get-in state (st/get-path state :content))
|
||||||
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
|
[cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y])
|
||||||
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
|
[ocx ocy] (if (= prefix :c1) [:c2x :c2y] [:c1x :c1y])
|
||||||
|
point (gpt/point (+ (get-in content [index :params cx]) dx)
|
||||||
|
(+ (get-in content [index :params cy]) dy))
|
||||||
opposite-index (ugp/opposite-index content index prefix)]
|
opposite-index (ugp/opposite-index content index prefix)]
|
||||||
(cond-> state
|
(cond-> state
|
||||||
:always
|
:always
|
||||||
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
|
(-> (update-in [:workspace-local :edit-path id :content-modifiers index] assoc
|
||||||
cx dx cy dy)
|
cx dx cy dy)
|
||||||
|
(assoc-in [:workspace-local :edit-path id :moving-handler] point))
|
||||||
|
|
||||||
(and match-opposite? opposite-index)
|
(and match-opposite? opposite-index)
|
||||||
(update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc
|
(update-in [:workspace-local :edit-path id :content-modifiers opposite-index] assoc
|
||||||
|
@ -63,7 +66,7 @@
|
||||||
[rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)]
|
[rch uch] (changes/generate-path-changes page-id shape (:content shape) new-content)]
|
||||||
|
|
||||||
(rx/of (dwc/commit-changes rch uch {:commit-local? true})
|
(rx/of (dwc/commit-changes rch uch {:commit-local? true})
|
||||||
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers)))))))
|
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))
|
||||||
|
|
||||||
(defn move-selected-path-point [from-point to-point]
|
(defn move-selected-path-point [from-point to-point]
|
||||||
(letfn [(modify-content-point [content {dx :x dy :y} modifiers point]
|
(letfn [(modify-content-point [content {dx :x dy :y} modifiers point]
|
||||||
|
@ -101,7 +104,9 @@
|
||||||
modifiers (->> points
|
modifiers (->> points
|
||||||
(reduce modifiers-reducer {}))]
|
(reduce modifiers-reducer {}))]
|
||||||
|
|
||||||
(assoc-in state [:workspace-local :edit-path id :content-modifiers] modifiers))))))
|
(-> state
|
||||||
|
(assoc-in [:workspace-local :edit-path id :moving-nodes] true)
|
||||||
|
(assoc-in [:workspace-local :edit-path id :content-modifiers] modifiers)))))))
|
||||||
|
|
||||||
(defn start-move-path-point
|
(defn start-move-path-point
|
||||||
[position shift?]
|
[position shift?]
|
||||||
|
@ -120,11 +125,6 @@
|
||||||
|
|
||||||
mouse-drag-stream
|
mouse-drag-stream
|
||||||
(rx/concat
|
(rx/concat
|
||||||
;; If we're dragging a selected item we don't change the selection
|
|
||||||
(if selected?
|
|
||||||
(rx/empty)
|
|
||||||
(rx/of (selection/select-node position shift?)))
|
|
||||||
|
|
||||||
;; This stream checks the consecutive mouse positions to do the draging
|
;; This stream checks the consecutive mouse positions to do the draging
|
||||||
(->> points
|
(->> points
|
||||||
(streams/move-points-stream start-position selected-points)
|
(streams/move-points-stream start-position selected-points)
|
||||||
|
|
|
@ -28,15 +28,6 @@
|
||||||
(let [id (st/get-path-id state)]
|
(let [id (st/get-path-id state)]
|
||||||
(update-in state [:workspace-local :edit-path id :hover-points] disj position)))))
|
(update-in state [:workspace-local :edit-path id :hover-points] disj position)))))
|
||||||
|
|
||||||
(defn select-handler [index type]
|
|
||||||
(ptk/reify ::select-handler
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [id (get-in state [:workspace-local :edition])]
|
|
||||||
(-> state
|
|
||||||
(update-in [:workspace-local :edit-path id :selected-handlers] (fnil conj #{}) [index type]))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn path-handler-enter [index prefix]
|
(defn path-handler-enter [index prefix]
|
||||||
(ptk/reify ::path-handler-enter
|
(ptk/reify ::path-handler-enter
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
|
@ -115,7 +106,6 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [id (st/get-path-id state)]
|
(let [id (st/get-path-id state)]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc-in [:workspace-local :edit-path id :selected-handlers] #{})
|
|
||||||
(assoc-in [:workspace-local :edit-path id :selected-points] #{}))))))
|
(assoc-in [:workspace-local :edit-path id :selected-points] #{}))))))
|
||||||
|
|
||||||
(defn update-area-selection
|
(defn update-area-selection
|
||||||
|
|
|
@ -58,36 +58,39 @@
|
||||||
[start-point selected-points points]
|
[start-point selected-points points]
|
||||||
|
|
||||||
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
||||||
ranges (snap/create-ranges selected-points points)
|
ranges (snap/create-ranges points selected-points)
|
||||||
d-pos (/ snap/snap-accuracy zoom)]
|
d-pos (/ snap/snap-path-accuracy zoom)
|
||||||
|
|
||||||
|
check-path-snap
|
||||||
|
(fn [position]
|
||||||
|
(let [delta (gpt/subtract position start-point)
|
||||||
|
moved-points (->> selected-points (mapv #(gpt/add % delta)))
|
||||||
|
snap (snap/get-snap-delta moved-points ranges d-pos)]
|
||||||
|
(gpt/add position snap)))]
|
||||||
(->> ms/mouse-position
|
(->> ms/mouse-position
|
||||||
(rx/map (fn [position]
|
(rx/map check-path-snap))))
|
||||||
(let [delta (gpt/subtract position start-point)
|
|
||||||
moved-points (->> selected-points (mapv #(gpt/add % delta)))]
|
|
||||||
(gpt/add
|
|
||||||
position
|
|
||||||
(snap/get-snap-delta moved-points ranges d-pos)))))))
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn move-handler-stream
|
(defn move-handler-stream
|
||||||
[start-point handler points]
|
[start-point handler points]
|
||||||
|
|
||||||
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
||||||
ranges (snap/create-ranges points)
|
ranges (snap/create-ranges points)
|
||||||
d-pos (/ snap/snap-accuracy zoom)]
|
d-pos (/ snap/snap-path-accuracy zoom)
|
||||||
|
|
||||||
|
check-path-snap
|
||||||
|
(fn [position]
|
||||||
|
(let [delta (gpt/subtract position start-point)
|
||||||
|
handler-position (gpt/add handler delta)
|
||||||
|
snap (snap/get-snap-delta [handler-position] ranges d-pos)]
|
||||||
|
(gpt/add position snap)))]
|
||||||
(->> ms/mouse-position
|
(->> ms/mouse-position
|
||||||
(rx/map (fn [position]
|
(rx/map check-path-snap))))
|
||||||
(let [delta (gpt/subtract position start-point)
|
|
||||||
handler-position (gpt/add handler delta)]
|
|
||||||
(gpt/add
|
|
||||||
position
|
|
||||||
(snap/get-snap-delta [handler-position] ranges d-pos))))))))
|
|
||||||
|
|
||||||
(defn position-stream
|
(defn position-stream
|
||||||
[points]
|
[points]
|
||||||
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
(let [zoom (get-in @st/state [:workspace-local :zoom] 1)
|
||||||
;; ranges (snap/create-ranges points)
|
;; ranges (snap/create-ranges points)
|
||||||
d-pos (/ snap/snap-accuracy zoom)
|
d-pos (/ snap/snap-path-accuracy zoom)
|
||||||
get-content (fn [state] (get-in state (state/get-path state :content)))
|
get-content (fn [state] (get-in state (state/get-path state :content)))
|
||||||
|
|
||||||
content-stream
|
content-stream
|
||||||
|
@ -103,9 +106,7 @@
|
||||||
(rx/with-latest vector ranges-stream)
|
(rx/with-latest vector ranges-stream)
|
||||||
(rx/map (fn [[position ranges]]
|
(rx/map (fn [[position ranges]]
|
||||||
(let [snap (snap/get-snap-delta [position] ranges d-pos)]
|
(let [snap (snap/get-snap-delta [position] ranges d-pos)]
|
||||||
#_(prn ">>>" snap)
|
(gpt/add position snap))))
|
||||||
(gpt/add position snap))
|
|
||||||
))
|
|
||||||
|
|
||||||
(rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %))))
|
(rx/with-latest merge (->> ms/mouse-position-shift (rx/map #(hash-map :shift? %))))
|
||||||
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))))
|
(rx/with-latest merge (->> ms/mouse-position-alt (rx/map #(hash-map :alt? %)))))))
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
|
||||||
(defonce ^:private snap-accuracy 5)
|
(defonce ^:private snap-accuracy 5)
|
||||||
|
(defonce ^:private snap-path-accuracy 10)
|
||||||
(defonce ^:private snap-distance-accuracy 10)
|
(defonce ^:private snap-distance-accuracy 10)
|
||||||
|
|
||||||
(defn- remove-from-snap-points
|
(defn- remove-from-snap-points
|
||||||
|
@ -272,9 +273,8 @@
|
||||||
(->> (rt/range-query (get ranges coord) (- pval precision) (+ pval precision))
|
(->> (rt/range-query (get ranges coord) (- pval precision) (+ pval precision))
|
||||||
;; We save the distance to the point and add the matching point to the points
|
;; We save the distance to the point and add the matching point to the points
|
||||||
(mapv (fn [[value points]]
|
(mapv (fn [[value points]]
|
||||||
[(mth/abs (- value pval))
|
[(- value pval)
|
||||||
(->> points (mapv #(vector point %)))])))))]
|
(->> points (mapv #(vector point %)))])))))]
|
||||||
|
|
||||||
{:x (query-coord point :x)
|
{:x (query-coord point :x)
|
||||||
:y (query-coord point :y)}))
|
:y (query-coord point :y)}))
|
||||||
|
|
||||||
|
@ -301,7 +301,7 @@
|
||||||
[default matches]
|
[default matches]
|
||||||
(let [get-min
|
(let [get-min
|
||||||
(fn [[cur-val :as current] [other-val :as other]]
|
(fn [[cur-val :as current] [other-val :as other]]
|
||||||
(if (< cur-val other-val)
|
(if (< (mth/abs cur-val) (mth/abs other-val))
|
||||||
current
|
current
|
||||||
other))
|
other))
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.geom.path :as ugp]
|
[app.util.geom.path :as ugp]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
|
[clojure.set :refer [map-invert]]
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
[rumext.alpha :as mf])
|
[rumext.alpha :as mf])
|
||||||
(:import goog.events.EventType))
|
(:import goog.events.EventType))
|
||||||
|
@ -42,7 +43,10 @@
|
||||||
(let [shift? (kbd/shift? event)]
|
(let [shift? (kbd/shift? event)]
|
||||||
(cond
|
(cond
|
||||||
(= edit-mode :move)
|
(= edit-mode :move)
|
||||||
(st/emit! (drp/start-move-path-point position shift?))
|
;; If we're dragging a selected item we don't change the selection
|
||||||
|
(do (when (not selected?)
|
||||||
|
(st/emit! (drp/select-node position shift?)))
|
||||||
|
(st/emit! (drp/start-move-path-point position shift?)))
|
||||||
|
|
||||||
(and (= edit-mode :draw) start-path?)
|
(and (= edit-mode :draw) start-path?)
|
||||||
(st/emit! (drp/start-path-from-point position))
|
(st/emit! (drp/start-path-from-point position))
|
||||||
|
@ -84,14 +88,6 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(st/emit! (drp/path-handler-leave index prefix)))
|
(st/emit! (drp/path-handler-leave index prefix)))
|
||||||
|
|
||||||
on-click
|
|
||||||
(fn [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(cond
|
|
||||||
(= edit-mode :move)
|
|
||||||
(drp/select-handler index prefix)))
|
|
||||||
|
|
||||||
on-mouse-down
|
on-mouse-down
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
@ -123,7 +119,6 @@
|
||||||
[:circle {:cx x
|
[:circle {:cx x
|
||||||
:cy y
|
:cy y
|
||||||
:r (/ 10 zoom)
|
:r (/ 10 zoom)
|
||||||
:on-click on-click
|
|
||||||
:on-mouse-down on-mouse-down
|
:on-mouse-down on-mouse-down
|
||||||
:on-mouse-enter on-enter
|
:on-mouse-enter on-enter
|
||||||
:on-mouse-leave on-leave
|
:on-mouse-leave on-leave
|
||||||
|
@ -145,7 +140,7 @@
|
||||||
:preview? true
|
:preview? true
|
||||||
:zoom zoom}]])
|
:zoom zoom}]])
|
||||||
|
|
||||||
(mf/defc snap-points [{:keys [selected points zoom]}]
|
(mf/defc path-snap [{:keys [selected points zoom]}]
|
||||||
(let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected))
|
(let [ranges (mf/use-memo (mf/deps selected points) #(snap/create-ranges points selected))
|
||||||
snap-matches (snap/get-snap-delta-match selected ranges (/ 1 zoom))
|
snap-matches (snap/get-snap-delta-match selected ranges (/ 1 zoom))
|
||||||
matches (d/concat [] (second (:x snap-matches)) (second (:y snap-matches)))]
|
matches (d/concat [] (second (:x snap-matches)) (second (:y snap-matches)))]
|
||||||
|
@ -172,19 +167,41 @@
|
||||||
preview
|
preview
|
||||||
content-modifiers
|
content-modifiers
|
||||||
last-point
|
last-point
|
||||||
selected-handlers
|
|
||||||
selected-points
|
selected-points
|
||||||
|
moving-nodes
|
||||||
|
moving-handler
|
||||||
hover-handlers
|
hover-handlers
|
||||||
hover-points]
|
hover-points]
|
||||||
:as edit-path} (mf/deref edit-path-ref)
|
:as edit-path} (mf/deref edit-path-ref)
|
||||||
|
|
||||||
{base-content :content} shape
|
selected-points (or selected-points #{})
|
||||||
|
|
||||||
|
base-content (:content shape)
|
||||||
|
base-points (mf/use-memo (mf/deps base-content) #(->> base-content ugp/content->points))
|
||||||
|
|
||||||
content (ugp/apply-content-modifiers base-content content-modifiers)
|
content (ugp/apply-content-modifiers base-content content-modifiers)
|
||||||
points (mf/use-memo (mf/deps content) #(->> content ugp/content->points (into #{})))
|
content-points (mf/use-memo (mf/deps content) #(->> content ugp/content->points))
|
||||||
|
|
||||||
|
point->base (->> (map hash-map content-points base-points) (reduce merge))
|
||||||
|
base->point (map-invert point->base)
|
||||||
|
|
||||||
|
points (into #{} content-points)
|
||||||
|
|
||||||
last-command (last content)
|
last-command (last content)
|
||||||
last-p (->> content last ugp/command->point)
|
last-p (->> content last ugp/command->point)
|
||||||
handlers (ugp/content->handlers content)
|
handlers (ugp/content->handlers content)
|
||||||
|
|
||||||
|
[snap-selected snap-points]
|
||||||
|
(cond
|
||||||
|
(some? drag-handler) [#{drag-handler} points]
|
||||||
|
(some? preview) [#{(ugp/command->point preview)} points]
|
||||||
|
(some? moving-handler) [#{moving-handler} points]
|
||||||
|
:else
|
||||||
|
[(->> selected-points (map base->point) (into #{}))
|
||||||
|
(->> points (remove selected-points) (into #{}))])
|
||||||
|
|
||||||
|
show-snap? (or (some? drag-handler) (some? preview) (some? moving-handler) moving-nodes)
|
||||||
|
|
||||||
handle-double-click-outside
|
handle-double-click-outside
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(when (= edit-mode :move)
|
(when (= edit-mode :move)
|
||||||
|
@ -199,13 +216,14 @@
|
||||||
|
|
||||||
[:g.path-editor {:ref editor-ref}
|
[:g.path-editor {:ref editor-ref}
|
||||||
(when (and preview (not drag-handler))
|
(when (and preview (not drag-handler))
|
||||||
[:*
|
[:& path-preview {:command preview
|
||||||
[:& snap-points {:selected #{(ugp/command->point preview)}
|
:from last-p
|
||||||
:points points
|
:zoom zoom}])
|
||||||
:zoom zoom}]
|
|
||||||
|
|
||||||
[:& path-preview {:command preview
|
(when drag-handler
|
||||||
:from last-p
|
[:g.drag-handler {:pointer-events "none"}
|
||||||
|
[:& path-handler {:point last-p
|
||||||
|
:handler drag-handler
|
||||||
:zoom zoom}]])
|
:zoom zoom}]])
|
||||||
|
|
||||||
(when @hover-point
|
(when @hover-point
|
||||||
|
@ -214,10 +232,11 @@
|
||||||
:zoom zoom}]])
|
:zoom zoom}]])
|
||||||
|
|
||||||
(for [position points]
|
(for [position points]
|
||||||
(let [point-selected? (contains? selected-points position)
|
(let [point-selected? (contains? selected-points (get point->base position))
|
||||||
point-hover? (contains? hover-points position)
|
point-hover? (contains? hover-points (get point->base position))
|
||||||
last-p? (= last-point position)
|
last-p? (= last-point (get point->base position))
|
||||||
start-p? (not (some? last-point))]
|
start-p? (not (some? last-point))]
|
||||||
|
|
||||||
[:g.path-node
|
[:g.path-node
|
||||||
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
|
[:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")}
|
||||||
(for [[index prefix] (get handlers position)]
|
(for [[index prefix] (get handlers position)]
|
||||||
|
@ -225,7 +244,6 @@
|
||||||
x (get-in command [:params (d/prefix-keyword prefix :x)])
|
x (get-in command [:params (d/prefix-keyword prefix :x)])
|
||||||
y (get-in command [:params (d/prefix-keyword prefix :y)])
|
y (get-in command [:params (d/prefix-keyword prefix :y)])
|
||||||
handler-position (gpt/point x y)
|
handler-position (gpt/point x y)
|
||||||
handler-selected? (contains? selected-handlers [index prefix])
|
|
||||||
handler-hover? (contains? hover-handlers [index prefix])]
|
handler-hover? (contains? hover-handlers [index prefix])]
|
||||||
(when (not= position handler-position)
|
(when (not= position handler-position)
|
||||||
[:& path-handler {:point position
|
[:& path-handler {:point position
|
||||||
|
@ -233,7 +251,6 @@
|
||||||
:index index
|
:index index
|
||||||
:prefix prefix
|
:prefix prefix
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:selected? handler-selected?
|
|
||||||
:hover? handler-hover?
|
:hover? handler-hover?
|
||||||
:edit-mode edit-mode}])))]
|
:edit-mode edit-mode}])))]
|
||||||
[:& path-point {:position position
|
[:& path-point {:position position
|
||||||
|
@ -250,9 +267,9 @@
|
||||||
:handler prev-handler
|
:handler prev-handler
|
||||||
:zoom zoom}]])
|
:zoom zoom}]])
|
||||||
|
|
||||||
(when drag-handler
|
(when show-snap?
|
||||||
[:g.drag-handler {:pointer-events "none"}
|
[:g.path-snap {:pointer-events "none"}
|
||||||
[:& path-handler {:point last-p
|
[:& path-snap {:selected snap-selected
|
||||||
:handler drag-handler
|
:points snap-points
|
||||||
:zoom zoom}]])]))
|
:zoom zoom}]])]))
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
(when (and (not= edition id) text-editing?)
|
(when (and (not= edition id) text-editing?)
|
||||||
(st/emit! dw/clear-edition-mode))
|
(st/emit! dw/clear-edition-mode))
|
||||||
|
|
||||||
(when (and (or (not edition) (not= edition id))
|
(when (and (not text-editing?)
|
||||||
(not blocked)
|
(not blocked)
|
||||||
(not hidden)
|
(not hidden)
|
||||||
(not (#{:comments :path} drawing-tool))
|
(not (#{:comments :path} drawing-tool))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue