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 {}}}})
|
:symbols {:main {:id :main :frames blank-frames :nodes {}}}})
|
||||||
|
|
||||||
(defn- transform-op
|
(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]
|
[op m path]
|
||||||
(let [at (fn [x y] [(+ (* (aget m 0) x) (* (aget m 2) y) (aget m 4))
|
(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))])
|
(+ (* (aget m 1) x) (* (aget m 3) y) (aget m 5))])
|
||||||
scale (node/mean-scale m)
|
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)
|
(case (:kind op)
|
||||||
:poly (let [out (js/Float64Array. (.-length (:pts op)))]
|
:poly (let [out (js/Float64Array. (.-length (:pts op)))]
|
||||||
(dotimes [i (:n op)]
|
(dotimes [i (:n op)]
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,18 @@
|
||||||
(fn [db [_ path]]
|
(fn [db [_ path]]
|
||||||
(update-in db [:ui :expanded] #(if (contains? % path) (disj % path) (conj % 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
|
(defn- where-new-goes
|
||||||
"The row path, from the open symbol down, of the symbol a new thing goes into:
|
"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
|
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 ::open (fn [db _] (get-in db [:ui :open])))
|
||||||
(rf/reg-sub ::sliding (fn [db _] (get-in db [:ui :sliding])))
|
(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
|
(rf/reg-sub
|
||||||
::clip
|
::clip
|
||||||
|
|
@ -91,6 +92,12 @@
|
||||||
;; new blocks, the resolver cannot find them, and the frame throws.
|
;; new blocks, the resolver cannot find them, and the frame throws.
|
||||||
(:store (footage/entry id))))
|
(: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
|
(rf/reg-sub
|
||||||
::resolver
|
::resolver
|
||||||
:<- [::clip]
|
:<- [::clip]
|
||||||
|
|
@ -101,3 +108,26 @@
|
||||||
(fn [[document sid store palette picture-fps] _]
|
(fn [[document sid store palette picture-fps] _]
|
||||||
(when (and document (clip/symbol document sid))
|
(when (and document (clip/symbol document sid))
|
||||||
(clip/resolver document store palette sid {:picture-fps picture-fps}))))
|
(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
|
(reset! tracker
|
||||||
(ratom/run!
|
(ratom/run!
|
||||||
(let [was (:resolver @snapshot)
|
(let [was (:resolver @snapshot)
|
||||||
now @(rf/subscribe [::render/resolver])]
|
now @(rf/subscribe [::render/shown])]
|
||||||
(reset! snapshot
|
(reset! snapshot
|
||||||
{:resolver now
|
{:resolver now
|
||||||
:palette @(rf/subscribe [::render/palette])
|
:palette @(rf/subscribe [::render/palette])
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
(cond (< y 0.3) :front (> y 0.7) :back :else :into)))
|
(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]}
|
(defn- label-cell [{:keys [path depth label kind node-kind select expandable? expanded? of]}
|
||||||
selection over]
|
selection over solo]
|
||||||
(let [node? (= :node kind)
|
(let [node? (= :node kind)
|
||||||
[over-path where] @over]
|
[over-path where] @over]
|
||||||
[:div (cond-> {:class (str "tl-label" (when (and select (= select selection)) " on")
|
[:div (cond-> {:class (str "tl-label" (when (and select (= select selection)) " on")
|
||||||
|
|
@ -260,6 +260,13 @@
|
||||||
(when expandable? (if expanded? "▾" "▸"))]
|
(when expandable? (if expanded? "▾" "▸"))]
|
||||||
[:span.name label]
|
[:span.name label]
|
||||||
(when node? [:span.kind (str "·" (name node-kind))])
|
(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))
|
(when (and node? (= select selection))
|
||||||
[:button.tl-delete {:title "delete, with everything in it (⌫)"
|
[:button.tl-delete {:title "delete, with everything in it (⌫)"
|
||||||
:on-click (fn [^js e]
|
:on-click (fn [^js e]
|
||||||
|
|
@ -329,6 +336,7 @@
|
||||||
selection @(rf/subscribe [::sub/selection])
|
selection @(rf/subscribe [::sub/selection])
|
||||||
expanded @(rf/subscribe [::sub/expanded])
|
expanded @(rf/subscribe [::sub/expanded])
|
||||||
drop @(rf/subscribe [::sub/drop])
|
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
|
;; 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
|
;; top: its own length, starting on the frame it would start on. The
|
||||||
;; stage's drop shows it too, at the playhead.
|
;; stage's drop shows it too, at the playhead.
|
||||||
|
|
@ -357,7 +365,7 @@
|
||||||
(rf/dispatch [::ui/move-node from []]))))}
|
(rf/dispatch [::ui/move-node from []]))))}
|
||||||
[:div.tl-corner]
|
[:div.tl-corner]
|
||||||
(doall (for [row visible]
|
(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
|
[:div.tl-tracks
|
||||||
{:on-drag-enter (fn [^js e] (when (drag/accepts?) (.preventDefault e)))
|
{:on-drag-enter (fn [^js e] (when (drag/accepts?) (.preventDefault e)))
|
||||||
:on-drag-over (fn [^js e]
|
:on-drag-over (fn [^js e]
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
(turn :main u [40 20] (/ js/Math.PI 2) 2)
|
(turn :main u [40 20] (/ js/Math.PI 2) 2)
|
||||||
(turn :mid v [5 -3] 0.3 1.5)
|
(turn :mid v [5 -3] 0.3 1.5)
|
||||||
(paint/new-shape :box :shape 4 [0 0 10 0 5 10] :brow))
|
(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))))))
|
((clip/resolver % nil pal/index-of :main) 16))))))
|
||||||
{:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16)
|
{:keys [frame matrix time]} (nest/inside c nil :main [u v :shape] 16)
|
||||||
out (js/Float64Array. 2)
|
out (js/Float64Array. 2)
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@
|
||||||
(let [before (snapshot (:clip entry) (:store entry) f)
|
(let [before (snapshot (:clip entry) (:store entry) f)
|
||||||
after (snapshot (:clip back) (:store back) f)]
|
after (snapshot (:clip back) (:store back) f)]
|
||||||
(is (seq before))
|
(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)
|
(is (= (snapshot (:clip entry) (:store entry) f)
|
||||||
(snapshot (:clip back) (:store back) 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-twist:disabled { opacity: 0; cursor: default; }
|
||||||
.tl-delete { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); }
|
.tl-delete { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); }
|
||||||
.tl-delete:hover { color: var(--fg); }
|
.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 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
|
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