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 :playing?] false))))
(rf/reg-event-db
(rf/reg-event-fx
::tick
(fn [db [_ f]]
(fn [{:keys [db]} [_ f]]
;; 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
;; 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.
(if (= f (get-in db [:playback :frame]))
db
(assoc-in db [:playback :frame] f))))
{:db db}
(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
::play

View file

@ -627,6 +627,8 @@
(fn [db _]
(update-in db [:ui :auto-key?] not)))
(declare record-auto-frame)
;; ---------------------------------------------------------------------------
;; a new symbol
@ -759,30 +761,59 @@
::gesture
;; 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.
;; Auto-key is different: its first movement must materialize the key, or the
;; channel's existing interpolation remains the document truth throughout the
;; drag and fights the hand moving it. Each move therefore updates the real key
;; while history/hold makes the entire gesture one undo step.
;; Auto-key records once per PLAYBACK FRAME, not once per pointer event. Pointer
;; events can arrive far faster than the document's fps and writing all of them
;; makes a drag needlessly expensive. `record-gesture` below is also dispatched
;; by playback/tick, so a held pointer records frames even while it is still.
(fn [db [_ g]]
(let [active (get-in db [:ui :gesture])
auto? (if active (:auto-key? active) (boolean (get-in db [:ui :auto-key?])))]
(if g
(let [{:keys [sid id frame values] :as g} (assoc g :auto-key? auto?)]
(cond-> db
(let [g (merge active g {:auto-key? auto?})
db (cond-> db
(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)
(: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
::transform
;; 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?]))]
(if auto?
(-> db
(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 auto?))
auto? (edit/history history/settle)))))
(seq values) (edit/edit #(gesture/apply-values % sid id frame values false))))))))
(rf/reg-event-db
::delete-selected

View file

@ -150,11 +150,11 @@
(when-let [{:keys [sid id frame] :as pl} (nest/placement document st open path f)]
(let [n (get-in document [:symbols sid :nodes id])
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})))))
(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)
moved? (or values (< 1 (js/Math.hypot (- (first p) (first p0)) (- (second p) (second p0)))))]
(when moved?
@ -174,14 +174,17 @@
t))))]
(when 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?]
(when-let [{:keys [pl values]} @gesture]
(when-let [{:keys [pl path open values]} @gesture]
(reset! gesture nil)
(when values
(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])))))
(defn- handles