Hold or tween any keyed channel's gaps, as a drawing's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-30 01:51:30 -04:00
parent da7e293814
commit 2a0426707a
7 changed files with 46 additions and 24 deletions

View file

@ -94,6 +94,16 @@
(:segments c) (update :segments dissoc f))
:else (ch/framed v)))))
(defn set-segment-interp
"Choose how channel `path`'s key at `left` leads to the next one: `:hold` cuts
there, `:linear` tweens. The same for a drawing's points as for a transform —
only a gap that exists, between a key and a later one, can be chosen."
[n path left interp]
(let [ks (:keys (get (channels n) path))]
(if (and (contains? ks left) (some #(< left %) (keys ks)) (#{:hold :linear} interp))
(assoc-in n [:channels path :segments left] interp)
n)))
;; ---------------------------------------------------------------------------
;; time maps
;;

View file

@ -46,13 +46,3 @@
(if (and points (< (inc i) (count points)))
(assoc-in clip path (-> points (assoc i x) (assoc (inc i) y)))
clip)))
(defn set-segment-interp [clip sid id key-frame interp]
(let [node (get-in clip [:symbols sid :nodes id])
keys (get-in node [:channels geometry :keys])]
(if (and (:paint? node) (contains? keys key-frame)
(some #(< key-frame %) (clojure.core/keys keys))
(#{:hold :linear} interp))
(assoc-in clip [:symbols sid :nodes id :channels geometry
:segments key-frame] interp)
clip)))

View file

@ -23,8 +23,3 @@
::set-vertex
(fn [db [_ sid id key-frame vertex point]]
(edit/edit db #(paint/set-vertex % sid id key-frame vertex point))))
(rf/reg-event-db
::set-segment-interp
(fn [db [_ sid id key-frame interp]]
(edit/edit db #(paint/set-segment-interp % sid id key-frame interp))))

View file

@ -624,6 +624,11 @@
(fn [db [_ sid id path frame]]
(edit/edit db #(update-in % [:symbols sid :nodes id] node/toggle-key path frame))))
(rf/reg-event-db
::set-segment-interp
(fn [db [_ sid id path left interp]]
(edit/edit db #(update-in % [:symbols sid :nodes id] node/set-segment-interp path left interp))))
(rf/reg-event-fx
::list
(fn [{:keys [db]} _]

View file

@ -123,6 +123,16 @@
:else (let [v (:value ch)]
(str "framed · " (if (channel/nothing? v) "absent" (pr-str v))))))
(defn- segment-select
"Hold or tween, for the gap after the key at `left` — the same choice on a
drawing as on a transform."
[sid id path ch left]
[:select.interp {:value (name (or (channel/segment-interp ch left) :hold))
:on-change #(rf/dispatch [::project/set-segment-interp
sid id path left (keyword (.. % -target -value))])}
[:option {:value "hold"} "hold"]
[:option {:value "linear"} "tween"]])
(defn- drawing-keys
"The polygon controls: jump to a drawing key, add one here, and choose what the
gap after the current one does. Lifted out of the old stage toolbar unchanged —
@ -158,23 +168,23 @@
(when next-k
[:div.row {:style {:margin-top "5px"}}
[:label.dim (str "key " active " → " next-k " ")
[:select {:value (name (or (channel/segment-interp geom active) :hold))
:on-change #(rf/dispatch [::paint-events/set-segment-interp
sid id active (keyword (.. % -target -value))])}
[:option {:value "hold"} "hold"]
[:option {:value "linear"} "tween"]]]])]))
[segment-select sid id paint/geometry geom active]]])]))
;; 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.
;; Rotation is shown in degrees. Between two keys, the gap after the one here
;; holds or tweens, as a drawing's does.
(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])
;; A boolean has nothing between true and false to tween through.
left (when (and keyed? frame (not (boolean? v))) (paint/active-frame ch frame))
gap? (and left (some #(< left %) (keys (:keys ch))))
put #(rf/dispatch [::project/set-channel sid id path frame %])
field (fn [i x on-number]
^{:key i}
@ -193,7 +203,8 @@
: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)))]))
v)))
(when gap? [segment-select sid id path ch left])]))
(defn- node-section [[sid id n]]
(let [[start end] (:span n)]

View file

@ -200,4 +200,13 @@
(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")))
(is (= :hold (get-in (node/toggle-key n [:vis] 0) [:channels [:vis] :interp])) "a boolean holds")
(let [h (node/set-segment-interp c [:xform :pos] 0 :hold)]
(is (= [5 5] (pos h 5)) "a gap set to hold cuts at the next key")
(is (= [10 0] (pos h 10)))
(is (= [7.5 2.5] (pos (node/set-segment-interp h [:xform :pos] 0 :linear) 5)) "and back to a tween")
(is (= h (node/set-segment-interp h [:xform :pos] 10 :hold)) "the last key has no gap after it")
(is (empty? (ch/problems (get-in h [:channels [:xform :pos]])))))
(let [d (node/toggle-key (node/set-segment-interp c [:xform :pos] 0 :hold) [:xform :pos] 0)]
(is (not (contains? (get-in d [:channels [:xform :pos] :segments]) 0))
"taking a key off takes its gap's choice with it"))))

View file

@ -3,6 +3,7 @@
[arthur.demo :as demo]
[arthur.domain.channel :as channel]
[arthur.domain.leaf :as leaf]
[arthur.domain.node :as node]
[arthur.domain.paint :as paint]
[arthur.domain.symbol :as symbol]))
@ -17,7 +18,8 @@
c3 (paint/add-key c2 :main :paint-test 15)
c4 (paint/set-vertex c3 :main :paint-test 15 0 [34 10])
held (geometry c4)
mixed-clip (paint/set-segment-interp c4 :main :paint-test 9 :linear)
mixed-clip (update-in c4 [:symbols :main :nodes :paint-test]
node/set-segment-interp paint/geometry 9 :linear)
mixed (geometry mixed-clip)]
(is (= [3 229] (get-in c2 [:symbols :main :nodes :paint-test :span])))
(is (= a (channel/value-at held 8)))