Solo an instance from its timeline row, shift-click for more than one
A nested mark is now named by the flat path its row has, [a b mark] rather than [a [b mark]], so a soloed row is a prefix of what it shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7aaf0a15bd
commit
da7e293814
8 changed files with 63 additions and 7 deletions
|
|
@ -135,12 +135,14 @@
|
|||
:symbols {:main {:id :main :frames blank-frames :nodes {}}}})
|
||||
|
||||
(defn- transform-op
|
||||
"Put a symbol's already resolved mark into its instance's parent space."
|
||||
"Put a symbol's already resolved mark into its instance's parent space. Its
|
||||
name becomes its path of instances down to it, the path its timeline row has."
|
||||
[op m path]
|
||||
(let [at (fn [x y] [(+ (* (aget m 0) x) (* (aget m 2) y) (aget m 4))
|
||||
(+ (* (aget m 1) x) (* (aget m 3) y) (aget m 5))])
|
||||
scale (node/mean-scale m)
|
||||
op (assoc op :node (conj path (:node op)))]
|
||||
n (:node op)
|
||||
op (assoc op :node (if (vector? n) (into path n) (conj path n)))]
|
||||
(case (:kind op)
|
||||
:poly (let [out (js/Float64Array. (.-length (:pts op)))]
|
||||
(dotimes [i (:n op)]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@
|
|||
(fn [db [_ path]]
|
||||
(update-in db [:ui :expanded] #(if (contains? % path) (disj % path) (conj % path)))))
|
||||
|
||||
(rf/reg-event-db
|
||||
::solo
|
||||
(fn [db [_ path more?]]
|
||||
;; Per open symbol, because a row path only means something from the symbol
|
||||
;; it was walked from. A click solos that row alone, or un-solos it if it
|
||||
;; already was; a shift-click adds it to or takes it out of the ones soloed.
|
||||
(update-in db [:ui :solo (get-in db [:ui :open])]
|
||||
(fn [on]
|
||||
(cond more? (if (contains? on path) (disj on path) (conj (set on) path))
|
||||
(= on #{path}) #{}
|
||||
:else #{path})))))
|
||||
|
||||
(defn- where-new-goes
|
||||
"The row path, from the open symbol down, of the symbol a new thing goes into:
|
||||
INSIDE the selected instance, or BESIDE any other selected node, or at the top
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
(rf/reg-sub ::open (fn [db _] (get-in db [:ui :open])))
|
||||
(rf/reg-sub ::sliding (fn [db _] (get-in db [:ui :sliding])))
|
||||
(rf/reg-sub ::solo (fn [db _] (get-in db [:ui :solo (get-in db [:ui :open])])))
|
||||
|
||||
(rf/reg-sub
|
||||
::clip
|
||||
|
|
@ -91,6 +92,12 @@
|
|||
;; new blocks, the resolver cannot find them, and the frame throws.
|
||||
(:store (footage/entry id))))
|
||||
|
||||
(defn- placed?
|
||||
"Does row `path` from symbol `sid` still name an instance, all the way down?"
|
||||
[clip sid path]
|
||||
(reduce (fn [sid id] (or (get-in clip [:symbols sid :nodes id :of]) (reduced nil)))
|
||||
sid path))
|
||||
|
||||
(rf/reg-sub
|
||||
::resolver
|
||||
:<- [::clip]
|
||||
|
|
@ -101,3 +108,26 @@
|
|||
(fn [[document sid store palette picture-fps] _]
|
||||
(when (and document (clip/symbol document sid))
|
||||
(clip/resolver document store palette sid {:picture-fps picture-fps}))))
|
||||
|
||||
(rf/reg-sub
|
||||
::shown
|
||||
:<- [::resolver]
|
||||
:<- [::clip]
|
||||
:<- [::open]
|
||||
:<- [::solo]
|
||||
(fn [[resolve document sid solo] _]
|
||||
;; What the stage draws: `::resolver`, less whatever is not under a soloed
|
||||
;; row. Its own layer, so soloing does not rebuild the resolver.
|
||||
;;
|
||||
;; A soloed row that has since been deleted or moved would otherwise leave
|
||||
;; the stage blank, with no row left to un-solo it from.
|
||||
(let [solo (filter #(placed? document sid %) solo)]
|
||||
(if (or (nil? resolve) (empty? solo))
|
||||
resolve
|
||||
;; An op inside an instance is named by the path its row has; one at the
|
||||
;; top by its bare id.
|
||||
(fn [f]
|
||||
(filterv (fn [{n :node}]
|
||||
(let [p (if (vector? n) n [n])]
|
||||
(some #(= % (take (count %) p)) solo)))
|
||||
(resolve f)))))))
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
(reset! tracker
|
||||
(ratom/run!
|
||||
(let [was (:resolver @snapshot)
|
||||
now @(rf/subscribe [::render/resolver])]
|
||||
now @(rf/subscribe [::render/shown])]
|
||||
(reset! snapshot
|
||||
{:resolver now
|
||||
:palette @(rf/subscribe [::render/palette])
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@
|
|||
(cond (< y 0.3) :front (> y 0.7) :back :else :into)))
|
||||
|
||||
(defn- label-cell [{:keys [path depth label kind node-kind select expandable? expanded? of]}
|
||||
selection over]
|
||||
selection over solo]
|
||||
(let [node? (= :node kind)
|
||||
[over-path where] @over]
|
||||
[:div (cond-> {:class (str "tl-label" (when (and select (= select selection)) " on")
|
||||
|
|
@ -260,6 +260,13 @@
|
|||
(when expandable? (if expanded? "▾" "▸"))]
|
||||
[:span.name label]
|
||||
(when node? [:span.kind (str "·" (name node-kind))])
|
||||
(when (= :instance node-kind)
|
||||
[:button {:class (str "tl-solo" (when (contains? solo path) " on"))
|
||||
:title "show only this on the stage (⇧ for more than one)"
|
||||
:on-click (fn [^js e]
|
||||
(.stopPropagation e)
|
||||
(rf/dispatch [::ui/solo path (.-shiftKey e)]))}
|
||||
"S"])
|
||||
(when (and node? (= select selection))
|
||||
[:button.tl-delete {:title "delete, with everything in it (⌫)"
|
||||
:on-click (fn [^js e]
|
||||
|
|
@ -329,6 +336,7 @@
|
|||
selection @(rf/subscribe [::sub/selection])
|
||||
expanded @(rf/subscribe [::sub/expanded])
|
||||
drop @(rf/subscribe [::sub/drop])
|
||||
solo (set @(rf/subscribe [::render/solo]))
|
||||
;; Where a drag out of the pool would land, as a row of its own at the
|
||||
;; top: its own length, starting on the frame it would start on. The
|
||||
;; stage's drop shows it too, at the playhead.
|
||||
|
|
@ -357,7 +365,7 @@
|
|||
(rf/dispatch [::ui/move-node from []]))))}
|
||||
[:div.tl-corner]
|
||||
(doall (for [row visible]
|
||||
^{:key (str (:path row))} [label-cell row selection over]))]
|
||||
^{:key (str (:path row))} [label-cell row selection over solo]))]
|
||||
[:div.tl-tracks
|
||||
{:on-drag-enter (fn [^js e] (when (drag/accepts?) (.preventDefault e)))
|
||||
:on-drag-over (fn [^js e]
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
(turn :main u [40 20] (/ js/Math.PI 2) 2)
|
||||
(turn :mid v [5 -3] 0.3 1.5)
|
||||
(paint/new-shape :box :shape 4 [0 0 10 0 5 10] :brow))
|
||||
draw #(take 6 (array-seq (:pts (first (filter (fn [op] (= [u [v :shape]] (:node op)))
|
||||
draw #(take 6 (array-seq (:pts (first (filter (fn [op] (= [u v :shape] (:node op)))
|
||||
((clip/resolver % nil pal/index-of :main) 16))))))
|
||||
{:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16)
|
||||
out (js/Float64Array. 2)
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
(let [before (snapshot (:clip entry) (:store entry) f)
|
||||
after (snapshot (:clip back) (:store back) f)]
|
||||
(is (seq before))
|
||||
(is (some #(= [:face-2 :mouth] (second (:node %))) before)))
|
||||
(is (some #(= [:face-2 :mouth] (rest (:node %))) before)))
|
||||
(is (= (snapshot (:clip entry) (:store entry) f)
|
||||
(snapshot (:clip back) (:store back) f))))))
|
||||
|
||||
|
|
|
|||
|
|
@ -646,6 +646,10 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
|||
.tl-twist:disabled { opacity: 0; cursor: default; }
|
||||
.tl-delete { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); }
|
||||
.tl-delete:hover { color: var(--fg); }
|
||||
.tl-solo { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); font-size: 10px; }
|
||||
.tl-solo:hover { color: var(--fg); }
|
||||
.tl-solo.on { color: var(--sel); font-weight: 600; }
|
||||
.tl-solo + .tl-delete { margin-left: 0; }
|
||||
|
||||
/* The ruler and the corner above the labels stay at the top of the body while
|
||||
the rows scroll under them: the ruler is the scrubber, and scrolling down to
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue