Key a node's transform and visibility from the inspector
Each of vis, anchor, pos, rot, scale and skew is a row: ◆ keys the channel on the node's own frame, or takes the key there off, and a typed value is written on blur or Enter — a key on a keyed channel, the one value on one that is not. Rotation reads in degrees. Dense and other channels keep their readout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
38850a29ba
commit
7246534505
5 changed files with 109 additions and 7 deletions
|
|
@ -70,6 +70,30 @@
|
|||
[n]
|
||||
(merge defaults (:channels n)))
|
||||
|
||||
(defn set-channel
|
||||
"Write `v` into channel `path`: a key on the node's own frame `f` when the
|
||||
channel is keyed, its one value when it is not."
|
||||
[n path f v]
|
||||
(let [c (get (channels n) path)]
|
||||
(assoc-in n [:channels path]
|
||||
(if (:keys c) (assoc-in c [:keys f] v) (ch/framed v)))))
|
||||
|
||||
(defn toggle-key
|
||||
"Key channel `path` on the node's own frame `f` with the value it has there, or
|
||||
take the key there off. The first key starts the channel animating and taking
|
||||
the last one off leaves it that one value. A boolean holds; anything else tweens."
|
||||
[n path f]
|
||||
(let [c (get (channels n) path)
|
||||
v (ch/value-at c f)
|
||||
ks (dissoc (:keys c) f)]
|
||||
(assoc-in n [:channels path]
|
||||
(cond
|
||||
(not (:keys c)) (ch/keyed {f v} (if (boolean? v) :hold :linear))
|
||||
(not (contains? (:keys c) f)) (assoc-in c [:keys f] v)
|
||||
(seq ks) (cond-> (assoc c :keys ks)
|
||||
(:segments c) (update :segments dissoc f))
|
||||
:else (ch/framed v)))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; time maps
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
[arthur.domain.bring :as bring]
|
||||
[arthur.domain.clip :as clip]
|
||||
[arthur.domain.leaf :as leaf]
|
||||
[arthur.domain.node :as node]
|
||||
[arthur.events.edit :as edit]
|
||||
[arthur.audio.mix :as mix]
|
||||
[arthur.demo.stage :as stage]
|
||||
|
|
@ -579,6 +580,17 @@
|
|||
:label (or (:label clip) (name id))
|
||||
:clip clip}}))))
|
||||
|
||||
(rf/reg-event-db
|
||||
::set-channel
|
||||
;; `frame` is the node's own, as for a drawing key.
|
||||
(fn [db [_ sid id path frame value]]
|
||||
(edit/edit db #(update-in % [:symbols sid :nodes id] node/set-channel path frame value))))
|
||||
|
||||
(rf/reg-event-db
|
||||
::toggle-key
|
||||
(fn [db [_ sid id path frame]]
|
||||
(edit/edit db #(update-in % [:symbols sid :nodes id] node/toggle-key path frame))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
::list
|
||||
(fn [{:keys [db]} _]
|
||||
|
|
|
|||
|
|
@ -122,6 +122,44 @@
|
|||
[:option {:value "hold"} "hold"]
|
||||
[:option {:value "linear"} "tween"]]]])]))
|
||||
|
||||
;; The transform and visibility, which every node has, as one row each: ◆ keys
|
||||
;; the channel here or takes the key here off, and an edit writes a key on a
|
||||
;; keyed channel or the one value on one that is not. `frame` is the node's own,
|
||||
;; nil when it is not on screen, where a keyed channel has no here to write to.
|
||||
;; Rotation is shown in degrees.
|
||||
|
||||
(defn- channel-control [sid id path ch frame]
|
||||
(let [keyed? (some? (:keys ch))
|
||||
v (channel/value-at ch (or frame 0))
|
||||
off? (and keyed? (nil? frame))
|
||||
deg? (= path [:xform :rot])
|
||||
put #(rf/dispatch [::project/set-channel sid id path frame %])
|
||||
;; Written on blur or Enter rather than per keystroke, so a typed
|
||||
;; number is one edit. Keyed by what it shows, so a seek or an undo
|
||||
;; that changes the value puts the new one in the box.
|
||||
field (fn [i x on-number]
|
||||
(let [shown (if deg? (/ (js/Math.round (* x (/ 18000 js/Math.PI))) 100) x)]
|
||||
^{:key (str i " " shown)}
|
||||
[:input {:type "number" :step "any" :disabled off?
|
||||
:default-value shown
|
||||
:on-key-down #(when (= "Enter" (.-key %)) (.. % -target blur))
|
||||
:on-blur (fn [e]
|
||||
(let [n (js/parseFloat (.. e -target -value))]
|
||||
(when-not (or (js/isNaN n) (= n shown))
|
||||
(on-number (if deg? (* n (/ js/Math.PI 180)) n)))))}]))]
|
||||
[:dd.channel
|
||||
[:button.key {:class (cond (contains? (:keys ch) frame) "on" keyed? "keyed")
|
||||
:disabled (nil? frame)
|
||||
:title (if keyed? (str (count (:keys ch)) " keys") "key this here")
|
||||
:on-click #(rf/dispatch [::project/toggle-key sid id path frame])}
|
||||
"◆"]
|
||||
(cond
|
||||
(boolean? v) [:input {:type "checkbox" :checked v :disabled off?
|
||||
:on-change #(put (.. % -target -checked))}]
|
||||
(number? v) (field 0 v put)
|
||||
:else (doall (map-indexed (fn [i x] (field i x #(put (assoc (vec v) i %))))
|
||||
v)))]))
|
||||
|
||||
(defn- node-section [[sid id n]]
|
||||
(let [[start end] (:span n)]
|
||||
[section (str (name (:kind n)) " · in " (name sid))
|
||||
|
|
@ -137,13 +175,16 @@
|
|||
"at" (when (= :map (get-in n [:time :mode])) (str (get-in n [:time :at] 0)))]
|
||||
(when (:paint? n) [drawing-keys sid id n @(rf/subscribe [::sub/selected-local])])
|
||||
[:div.row {:style {:margin-top "6px"}} [:span.dim "channels"]]
|
||||
[:dl.facts
|
||||
(doall
|
||||
(for [[path ch] (sort-by (comp str key) (node/channels n))]
|
||||
^{:key (str path)}
|
||||
[:<>
|
||||
[:dt (str/join " " (map name path))]
|
||||
[:dd (channel-state ch)]]))]]))
|
||||
(let [{:keys [frame]} @(rf/subscribe [::sub/selected-local])]
|
||||
[:dl.facts
|
||||
(doall
|
||||
(for [[path ch] (sort-by (comp str key) (node/channels n))]
|
||||
^{:key (str path)}
|
||||
[:<>
|
||||
[:dt (str/join " " (map name path))]
|
||||
(if (and (contains? node/defaults path) (not (:dense ch)))
|
||||
[channel-control sid id path ch frame]
|
||||
[:dd (channel-state ch)])]))])]))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; a symbol
|
||||
|
|
|
|||
|
|
@ -183,3 +183,21 @@
|
|||
(is (seq (node/problems {:id :x :kind :poly :z "a1"
|
||||
:channels {[:geom :pts] {:animated? true}}}))
|
||||
"and a channel that is malformed in itself"))
|
||||
|
||||
(deftest keying-a-channel-from-the-inspector
|
||||
(let [n {:id :x :kind :group}
|
||||
a (node/set-channel n [:xform :rot] 3 1.0)
|
||||
b (node/toggle-key a [:xform :rot] 3)
|
||||
c (-> b (node/set-channel [:xform :pos] 9 [5 5])
|
||||
(node/toggle-key [:xform :pos] 0)
|
||||
(node/set-channel [:xform :pos] 10 [10 0]))
|
||||
rot #(ch/value-at (get (node/channels %1) [:xform :rot]) %2)
|
||||
pos #(ch/value-at (get (node/channels %1) [:xform :pos]) %2)]
|
||||
(is (= 1.0 (rot a 50)) "an unkeyed channel is its one value")
|
||||
(is (= {3 1.0} (get-in b [:channels [:xform :rot] :keys])) "the first key is its value here")
|
||||
(is (= [7.5 2.5] (pos c 5)) "an edit on a keyed channel keys it, and keys tween")
|
||||
(is (= 1.0 (rot (node/set-channel b [:xform :rot] 8 2.0) 3)) "without moving the key before it")
|
||||
(let [d (node/toggle-key b [:xform :rot] 3)]
|
||||
(is (not (:animated? (get-in d [:channels [:xform :rot]]))) "the last key off is one value again")
|
||||
(is (= 1.0 (rot d 0))))
|
||||
(is (= :hold (get-in (node/toggle-key n [:vis] 0) [:channels [:vis] :interp])) "a boolean holds")))
|
||||
|
|
|
|||
|
|
@ -782,3 +782,10 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
|||
padding: 8px 10px;
|
||||
border-top: 1px solid var(--hair);
|
||||
}
|
||||
|
||||
/* A transform or visibility channel: its key button, then a field per component. */
|
||||
.facts dd.channel { display: flex; gap: 3px; align-items: center; overflow: visible; }
|
||||
.facts dd.channel input[type=number] { width: 0; flex: 1; min-width: 0; }
|
||||
.facts dd.channel .key { padding: 0 3px; color: var(--dim); }
|
||||
.facts dd.channel .key.keyed { color: var(--fg); }
|
||||
.facts dd.channel .key.on { color: var(--sel); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue