mirror of
https://github.com/penpot/penpot.git
synced 2025-05-30 21:46:12 +02:00
✨ Add the Shift+ctrl+drag to deselect (#6494)
* ✨ Allow shape deselection using Ctrl+Shift+Drag * ✨ Allow point deselection using Ctrl+Shift+Drag * ✨ Properly remember previous selection during addition/removal of shapes * ⚡ Preload point selection in path handle-area-selection Also: prefer dm/get-in over get-in * ✨ Highlight path nodes in selection rectangle incrementally
This commit is contained in:
parent
31f642ed25
commit
48a3d38d82
3 changed files with 63 additions and 47 deletions
|
@ -14,6 +14,7 @@
|
|||
[app.main.streams :as ms]
|
||||
[app.util.mouse :as mse]
|
||||
[beicon.v2.core :as rx]
|
||||
[beicon.v2.operators :as rxo]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn path-pointer-enter [position]
|
||||
|
@ -45,7 +46,7 @@
|
|||
(update-in state [:workspace-local :edit-path id :hover-handlers] disj [index prefix])))))
|
||||
|
||||
(defn select-node-area
|
||||
[shift?]
|
||||
[initial-set remove?]
|
||||
(ptk/reify ::select-node-area
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -57,13 +58,12 @@
|
|||
(partial gsh/has-point-rect? selrect)
|
||||
(constantly false))
|
||||
|
||||
selected-points (dm/get-in state [:workspace-local :edit-path id :selected-points])
|
||||
selected-points (or selected-points #{})
|
||||
|
||||
xform (comp (filter #(not (= (:command %) :close-path)))
|
||||
(map (comp gpt/point :params))
|
||||
(filter selected-point?))
|
||||
positions (into (if shift? selected-points #{}) xform content)]
|
||||
positions (if remove?
|
||||
(apply disj initial-set (into #{} xform content))
|
||||
(into initial-set xform content))]
|
||||
|
||||
(cond-> state
|
||||
(some? id)
|
||||
|
@ -73,8 +73,8 @@
|
|||
(ptk/reify ::select-node
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (get-in state [:workspace-local :edition])
|
||||
selected-points (or (get-in state [:workspace-local :edit-path id :selected-points]) #{})
|
||||
(let [id (dm/get-in state [:workspace-local :edition])
|
||||
selected-points (dm/get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
selected-points (cond
|
||||
(and shift? (contains? selected-points position))
|
||||
(disj selected-points position)
|
||||
|
@ -111,25 +111,42 @@
|
|||
(update state :workspace-local dissoc :selrect))))
|
||||
|
||||
(defn handle-area-selection
|
||||
[shift?]
|
||||
[append? remove?]
|
||||
(letfn [(valid-rect? [zoom {width :width height :height}]
|
||||
(or (> width (/ 10 zoom)) (> height (/ 10 zoom))))]
|
||||
|
||||
(ptk/reify ::handle-area-selection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [zoom (get-in state [:workspace-local :zoom] 1)
|
||||
(let [id (dm/get-in state [:workspace-local :edition])
|
||||
zoom (dm/get-in state [:workspace-local :zoom] 1)
|
||||
stopper (mse/drag-stopper stream)
|
||||
from-p @ms/mouse-position]
|
||||
(rx/concat
|
||||
(->> ms/mouse-position
|
||||
(rx/map #(grc/points->rect [from-p %]))
|
||||
(rx/filter (partial valid-rect? zoom))
|
||||
(rx/map update-area-selection)
|
||||
(rx/take-until stopper))
|
||||
from-p @ms/mouse-position
|
||||
|
||||
(rx/of (select-node-area shift?)
|
||||
(clear-area-selection))))))))
|
||||
initial-set
|
||||
(if (or append? remove?)
|
||||
(dm/get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
#{})
|
||||
|
||||
selrect-stream
|
||||
(->> ms/mouse-position
|
||||
(rx/map #(grc/points->rect [from-p %]))
|
||||
(rx/filter (partial valid-rect? zoom))
|
||||
(rx/take-until stopper))]
|
||||
|
||||
(rx/concat
|
||||
(if (or append? remove?)
|
||||
(rx/empty)
|
||||
(rx/of (deselect-all)))
|
||||
(rx/merge
|
||||
(->> selrect-stream
|
||||
(rx/map update-area-selection))
|
||||
(->> selrect-stream
|
||||
(rx/buffer-time 100)
|
||||
(rx/map last)
|
||||
(rx/pipe (rxo/distinct-contiguous))
|
||||
(rx/map #(select-node-area initial-set remove?))))
|
||||
(rx/of (clear-area-selection))))))))
|
||||
|
||||
(defn update-selection
|
||||
[point-change]
|
||||
|
@ -137,7 +154,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [id (st/get-path-id state)
|
||||
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
selected-points (dm/get-in state [:workspace-local :edit-path id :selected-points] #{})
|
||||
selected-points (into #{} (map point-change) selected-points)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :selected-points] selected-points))))))
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
(assoc-in state [:workspace-local :selrect] selrect))))
|
||||
|
||||
(defn handle-area-selection
|
||||
[preserve?]
|
||||
[append? remove? ignore-groups?]
|
||||
(ptk/reify ::handle-area-selection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -62,6 +62,10 @@
|
|||
stopper (mse/drag-stopper stream)
|
||||
init-position @ms/mouse-position
|
||||
|
||||
initial-set (if (or append? remove?)
|
||||
(dsh/lookup-selected state)
|
||||
lks/empty-linked-set)
|
||||
|
||||
init-selrect (grc/make-rect
|
||||
(dm/get-prop init-position :x)
|
||||
(dm/get-prop init-position :y)
|
||||
|
@ -91,7 +95,7 @@
|
|||
(rx/take-until stopper))]
|
||||
|
||||
(rx/concat
|
||||
(if preserve?
|
||||
(if (or append? remove?)
|
||||
(rx/empty)
|
||||
(rx/of (deselect-all)))
|
||||
|
||||
|
@ -103,20 +107,14 @@
|
|||
(rx/buffer-time 100)
|
||||
(rx/map last)
|
||||
(rx/pipe (rxo/distinct-contiguous))
|
||||
(rx/with-latest-from ms/keyboard-mod ms/keyboard-shift)
|
||||
(rx/map
|
||||
(fn [[_ mod? shift?]]
|
||||
(select-shapes-by-current-selrect shift? mod?))))
|
||||
(rx/map #(select-shapes-by-current-selrect initial-set remove? ignore-groups?)))
|
||||
|
||||
;; The last "tick" from the mouse cannot be buffered so we are sure
|
||||
;; a selection is returned. Without this we can have empty selections on
|
||||
;; very fast movement
|
||||
(->> selrect-stream
|
||||
(rx/last)
|
||||
(rx/with-latest-from ms/keyboard-mod ms/keyboard-shift)
|
||||
(rx/map
|
||||
(fn [[_ mod? shift?]]
|
||||
(select-shapes-by-current-selrect shift? mod? false)))))
|
||||
(rx/map #(select-shapes-by-current-selrect initial-set remove? ignore-groups? false))))
|
||||
|
||||
(->> (rx/of (update-selrect nil))
|
||||
;; We need the async so the current event finishes before updating the selrect
|
||||
|
@ -325,22 +323,23 @@
|
|||
;; --- Select Shapes (By selrect)
|
||||
|
||||
(defn select-shapes-by-current-selrect
|
||||
([preserve? ignore-groups?]
|
||||
(select-shapes-by-current-selrect preserve? ignore-groups? true))
|
||||
([preserve? ignore-groups? buffered?]
|
||||
"Sends the current selection rectangle to the worker to compute the selection,
|
||||
and sends its result to select-shapes for storage in the state."
|
||||
([initial-set remove? ignore-groups?]
|
||||
(select-shapes-by-current-selrect initial-set remove? ignore-groups? true))
|
||||
|
||||
([initial-set remove? ignore-groups? buffered?]
|
||||
(ptk/reify ::select-shapes-by-current-selrect
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
selected (dsh/lookup-selected state)
|
||||
initial-set (if preserve?
|
||||
selected
|
||||
lks/empty-linked-set)
|
||||
selrect (dm/get-in state [:workspace-local :selrect])
|
||||
blocked? (fn [id] (dm/get-in objects [id :blocked] false))
|
||||
|
||||
ask-worker (if buffered? mw/ask-buffered! mw/ask!)]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
selrect (dm/get-in state [:workspace-local :selrect])
|
||||
blocked? (fn [id] (dm/get-in objects [id :blocked] false))
|
||||
ask-worker (if buffered? mw/ask-buffered! mw/ask!)
|
||||
filter-objs (comp
|
||||
(filter (complement blocked?))
|
||||
(remove (partial cfh/hidden-parent? objects)))]
|
||||
|
||||
(if (some? selrect)
|
||||
(->> (ask-worker
|
||||
|
@ -353,9 +352,9 @@
|
|||
:using-selrect? true})
|
||||
(rx/filter some?)
|
||||
(rx/map #(cfh/clean-loops objects %))
|
||||
(rx/map #(into initial-set (comp
|
||||
(filter (complement blocked?))
|
||||
(remove (partial cfh/hidden-parent? objects))) %))
|
||||
(rx/map (if remove?
|
||||
#(apply disj initial-set %)
|
||||
#(into initial-set filter-objs %)))
|
||||
(rx/map select-shapes))
|
||||
(rx/empty)))))))
|
||||
|
||||
|
|
|
@ -102,14 +102,14 @@
|
|||
node-editing?
|
||||
;; Handle path node area selection
|
||||
(when-not read-only?
|
||||
(st/emit! (dwdp/handle-area-selection shift?)))
|
||||
(st/emit! (dwdp/handle-area-selection shift? (and shift? mod?))))
|
||||
|
||||
drawing-tool
|
||||
(when-not read-only?
|
||||
(st/emit! (dd/start-drawing drawing-tool)))
|
||||
|
||||
(or (not id) mod?)
|
||||
(st/emit! (dw/handle-area-selection shift?))
|
||||
(st/emit! (dw/handle-area-selection shift? (and shift? mod?) mod?))
|
||||
|
||||
(not drawing-tool)
|
||||
(when-not read-only?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue