Sample auto-key drags once per playback frame

This commit is contained in:
Your Name 2026-10-01 12:30:24 -04:00
parent 6e9409b0de
commit abefa1c452
3 changed files with 58 additions and 22 deletions

View file

@ -47,16 +47,18 @@
(assoc-in [:playback :frame] 0) (assoc-in [:playback :frame] 0)
(assoc-in [:playback :playing?] false)))) (assoc-in [:playback :playing?] false))))
(rf/reg-event-db (rf/reg-event-fx
::tick ::tick
(fn [db [_ f]] (fn [{:keys [db]} [_ f]]
;; Written from the rAF loop when the DERIVED frame changes — not every ;; Written from the rAF loop when the DERIVED frame changes — not every
;; animation frame, and never as the thing the blit waits on. The picture is ;; animation frame, and never as the thing the blit waits on. The picture is
;; painted from the clock directly; this only brings the document's idea of ;; painted from the clock directly; this only brings the document's idea of
;; the playhead up to date so the readout and the scrubber agree with it. ;; the playhead up to date so the readout and the scrubber agree with it.
(if (= f (get-in db [:playback :frame])) (if (= f (get-in db [:playback :frame]))
db {:db db}
(assoc-in db [:playback :frame] f)))) (cond-> {:db (assoc-in db [:playback :frame] f)}
(get-in db [:ui :gesture :auto-key?])
(assoc :dispatch [:arthur.events.ui/record-gesture f])))))
(rf/reg-event-fx (rf/reg-event-fx
::play ::play

View file

@ -627,6 +627,8 @@
(fn [db _] (fn [db _]
(update-in db [:ui :auto-key?] not))) (update-in db [:ui :auto-key?] not)))
(declare record-auto-frame)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; a new symbol ;; a new symbol
@ -759,30 +761,59 @@
::gesture ::gesture
;; A transform in the middle of a drag on the stage, `{:sid :id :frame ;; A transform in the middle of a drag on the stage, `{:sid :id :frame
;; :values}`, drawn by `::render/clip` as `::sliding` is; nil when abandoned. ;; :values}`, drawn by `::render/clip` as `::sliding` is; nil when abandoned.
;; Auto-key is different: its first movement must materialize the key, or the ;; Auto-key records once per PLAYBACK FRAME, not once per pointer event. Pointer
;; channel's existing interpolation remains the document truth throughout the ;; events can arrive far faster than the document's fps and writing all of them
;; drag and fights the hand moving it. Each move therefore updates the real key ;; makes a drag needlessly expensive. `record-gesture` below is also dispatched
;; while history/hold makes the entire gesture one undo step. ;; by playback/tick, so a held pointer records frames even while it is still.
(fn [db [_ g]] (fn [db [_ g]]
(let [active (get-in db [:ui :gesture]) (let [active (get-in db [:ui :gesture])
auto? (if active (:auto-key? active) (boolean (get-in db [:ui :auto-key?])))] auto? (if active (:auto-key? active) (boolean (get-in db [:ui :auto-key?])))]
(if g (if g
(let [{:keys [sid id frame values] :as g} (assoc g :auto-key? auto?)] (let [g (merge active g {:auto-key? auto?})
(cond-> db db (cond-> db
(and auto? (nil? active)) (edit/history history/hold) (and auto? (nil? active)) (edit/history history/hold)
auto? (edit/edit #(gesture/apply-values % sid id frame values true)) true (assoc-in [:ui :gesture] g))]
true (assoc-in [:ui :gesture] g))) ;; Materialize the current frame immediately. More pointer moves in the
;; same frame only replace the preview; pointer-up forces its last value.
(if auto?
(record-auto-frame db (get-in db [:playback :frame]) false)
db))
(cond-> (update db :ui dissoc :gesture) (cond-> (update db :ui dissoc :gesture)
(:auto-key? active) (edit/history history/settle)))))) (:auto-key? active) (edit/history history/settle))))))
(defn record-auto-frame
"Write the active gesture once at outer playback frame `f`. `force?` replaces
the value already sampled for that frame, used for the final pointer value."
[db f force?]
(let [{:keys [auto-key? recorded-frame open path values] :as g}
(get-in db [:ui :gesture])]
(if (and auto-key? (seq values) (or force? (not= f recorded-frame)))
(let [{document :clip st :store} (store/entry (:clip/current db))]
(if-let [{:keys [sid id frame]} (nest/placement document st open path f)]
(-> db
(edit/edit #(gesture/apply-values % sid id frame values true))
(assoc-in [:ui :gesture] (assoc g :recorded-frame f)))
db))
db)))
(rf/reg-event-db
::record-gesture
(fn [db [_ f]] (record-auto-frame db f false)))
(rf/reg-event-db (rf/reg-event-db
::transform ::transform
;; The drag let go: one edit, so one undo step and one write to collaborators. ;; The drag let go: one edit, so one undo step and one write to collaborators.
(fn [db [_ {:keys [sid id frame values]}]] (fn [db [_ g]]
(let [auto? (boolean (get-in db [:ui :gesture :auto-key?]))] (let [auto? (boolean (get-in db [:ui :gesture :auto-key?]))]
(cond-> (update db :ui dissoc :gesture) (if auto?
(seq values) (edit/edit #(gesture/apply-values % sid id frame values auto?)) (-> db
auto? (edit/history history/settle))))) (update-in [:ui :gesture] merge g)
(record-auto-frame (get-in db [:playback :frame]) true)
(update :ui dissoc :gesture)
(edit/history history/settle))
(let [{:keys [sid id frame values]} g]
(cond-> (update db :ui dissoc :gesture)
(seq values) (edit/edit #(gesture/apply-values % sid id frame values false))))))))
(rf/reg-event-db (rf/reg-event-db
::delete-selected ::delete-selected

View file

@ -150,11 +150,11 @@
(when-let [{:keys [sid id frame] :as pl} (nest/placement document st open path f)] (when-let [{:keys [sid id frame] :as pl} (nest/placement document st open path f)]
(let [n (get-in document [:symbols sid :nodes id]) (let [n (get-in document [:symbols sid :nodes id])
v0 (gesture/values n frame st)] v0 (gesture/values n frame st)]
(reset! gesture {:kind kind :pl pl :v0 v0 :p0 p :n n (reset! gesture {:kind kind :pl pl :path path :open open :v0 v0 :p0 p :n n
:a (gesture/angle pl v0 p) :turned 0}))))) :a (gesture/angle pl v0 p) :turned 0})))))
(defn- drag! [p ^js event] (defn- drag! [p ^js event]
(let [{:keys [kind pl v0 p0 n a turned values]} @gesture (let [{:keys [kind pl path open v0 p0 n a turned values]} @gesture
shift? (.-shiftKey event) shift? (.-shiftKey event)
moved? (or values (< 1 (js/Math.hypot (- (first p) (first p0)) (- (second p) (second p0)))))] moved? (or values (< 1 (js/Math.hypot (- (first p) (first p0)) (- (second p) (second p0)))))]
(when moved? (when moved?
@ -174,14 +174,17 @@
t))))] t))))]
(when vs (when vs
(swap! gesture assoc :values vs) (swap! gesture assoc :values vs)
(rf/dispatch [::ui/gesture (assoc (select-keys pl [:sid :id :frame]) :values vs)]))))))) (rf/dispatch [::ui/gesture
(assoc (select-keys pl [:sid :id :frame])
:path path :open open :values vs)])))))))
(defn- let-go! [commit?] (defn- let-go! [commit?]
(when-let [{:keys [pl values]} @gesture] (when-let [{:keys [pl path open values]} @gesture]
(reset! gesture nil) (reset! gesture nil)
(when values (when values
(rf/dispatch (if commit? (rf/dispatch (if commit?
[::ui/transform (assoc (select-keys pl [:sid :id :frame]) :values values)] [::ui/transform (assoc (select-keys pl [:sid :id :frame])
:path path :open open :values values)]
[::ui/gesture nil]))))) [::ui/gesture nil])))))
(defn- handles (defn- handles