Align stage multi-selection modifiers with the timeline

This commit is contained in:
Olive Vaughn 2026-10-04 02:12:06 -04:00
parent 9bc406f413
commit 6de71c4c66
3 changed files with 38 additions and 6 deletions

View file

@ -7,7 +7,7 @@
WHICH LEVEL a click selects is Figma's and Illustrator's rule, and Flash's
without its edit mode: a click selects the thing in the open symbol, a
double-click goes one level into what is selected, ⌘-click goes straight to the
double-click goes one level into what is selected, ⌥-click goes straight to the
shape itself. A click inside what is selected keeps it, so a deep selection can
be dragged; one elsewhere selects at the same depth, beside it."
(:require [arthur.domain.channel :as ch]

View file

@ -29,6 +29,7 @@
[arthur.ui.player :as player]
[arthur.ui.tracing :as tracing]
[arthur.ui.tools :as tools]
[arthur.ui.timeline :as timeline]
[re-frame.core :as rf]
[reagent.core :as r]))
@ -55,6 +56,7 @@
;; produces go straight out as `::set-vertex`, which is where the document
;; changes and where re-frame belongs.
(defonce ^:private dragging (atom nil))
(defonce ^:private selection-anchor (atom nil))
(defn- editing
"The selected node when it is a polygon on screen, however deep it is nested,
@ -550,20 +552,35 @@
(do (.setPointerCapture svg (.-pointerId event))
(stroke-begin! ctx tool p size tone event))
(let [path (pick/choose selected (player/at p)
(or (.-metaKey event) (.-ctrlKey event)))]
(let [path (pick/choose selected (player/at p) (.-altKey event))]
(when (and path (not (.-shiftKey event)))
(reset! selection-anchor {:clip-id clip-id :open (:open ctx) :path path}))
(cond
(and (.-shiftKey event) path)
(and (or (.-metaKey event) (.-ctrlKey event)) path)
(when-let [address (address-for ctx path)]
(rf/dispatch [::ui/toggle-selection address]))
(and (.-shiftKey event) path)
(when-let [address (address-for ctx path)]
(let [document (:clip (loaded ctx))
expanded @(rf/subscribe [::sub/expanded])
xs (vec (keep :select (timeline/rows document (:open ctx) expanded selected)))
origin (if (and (= clip-id (:clip-id @selection-anchor))
(= (:open ctx) (:open @selection-anchor)))
(:path @selection-anchor) selected)
anchor (when origin (address-for ctx origin))
a (.indexOf xs anchor) b (.indexOf xs address)]
(rf/dispatch (if (and (<= 0 a) (<= 0 b))
[::ui/select-many (subvec xs (min a b) (inc (max a b)))]
[::ui/select address]))))
path
(when-not (contains? selected-paths path) (select! ctx path))
:else
(do (.setPointerCapture svg (.-pointerId event))
(reset! marquee {:p0 p :p p :more? (.-shiftKey event)})))
(when (and path (not (.-shiftKey event)))
(reset! marquee {:p0 p :p p :more? (or (.-metaKey event) (.-ctrlKey event) (.-shiftKey event))})))
(when (and path (not (or (.-shiftKey event) (.-metaKey event) (.-ctrlKey event))))
(.setPointerCapture svg (.-pointerId event))
(if (and (> (count placements) 1) (contains? selected-paths path))
(begin-many! ctx :move placements p)

View file

@ -484,3 +484,18 @@
(is (= [:girl clip-id] path))
(is (some? (get-in saved [:symbols :main :nodes clip-id]))
"and that is where the node actually is")))
(deftest restacking-a-selection-keeps-all-members-selected
(let [doc (update-in (fixture/document) [:symbols :main] dissoc :display)
id (store/install! {:clip doc :store {}} "restack-many-test")
selections [[:node :main :a [:a]] [:node :main :b [:b]]]]
(reset! rf-db/app-db {:clip/current id :paint/revision 0
:ui {:open :main :selection (peek selections) :selections selections}
:playback {:frame 6}})
(rf/dispatch-sync [::ui/restack-nodes selections [:insert] true])
(let [after (:clip (store/entry (:clip/current @rf-db/app-db)))
z #(get-in after [:symbols :main :nodes % :z])]
(is (pos? (compare (z :a) (z :insert))))
(is (pos? (compare (z :b) (z :a))))
(is (= selections (get-in @rf-db/app-db [:ui :selections])))
(is (= [0 4] (mapv #(get-in after [:symbols :main :nodes % :time :at]) [:a :b]))))))