From abefa1c452bd32703ad4c9985f58b6d10078daab Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 1 Oct 2026 12:30:24 -0400 Subject: [PATCH] Sample auto-key drags once per playback frame --- frontend/src/arthur/events/playback.cljs | 10 +++-- frontend/src/arthur/events/ui.cljs | 57 ++++++++++++++++++------ frontend/src/arthur/ui/stage.cljs | 13 +++--- 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/frontend/src/arthur/events/playback.cljs b/frontend/src/arthur/events/playback.cljs index a4794a1..c79e5e2 100644 --- a/frontend/src/arthur/events/playback.cljs +++ b/frontend/src/arthur/events/playback.cljs @@ -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 diff --git a/frontend/src/arthur/events/ui.cljs b/frontend/src/arthur/events/ui.cljs index ce7e5be..7ce9dfa 100644 --- a/frontend/src/arthur/events/ui.cljs +++ b/frontend/src/arthur/events/ui.cljs @@ -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 - (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))) + (let [g (merge active g {:auto-key? auto?}) + db (cond-> db + (and auto? (nil? active)) (edit/history history/hold) + 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?]))] - (cond-> (update db :ui dissoc :gesture) - (seq values) (edit/edit #(gesture/apply-values % sid id frame values auto?)) - auto? (edit/history history/settle))))) + (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 false)))))))) (rf/reg-event-db ::delete-selected diff --git a/frontend/src/arthur/ui/stage.cljs b/frontend/src/arthur/ui/stage.cljs index a55999d..76470b3 100644 --- a/frontend/src/arthur/ui/stage.cljs +++ b/frontend/src/arthur/ui/stage.cljs @@ -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