Fix palette swap backgrounds and timeline drop jitter

This commit is contained in:
Your Name 2026-10-02 10:24:23 -04:00
parent 26fab9392e
commit 7c49c08bc7
8 changed files with 55 additions and 20 deletions

View file

@ -243,12 +243,17 @@
(assoc op :cx x :cy y :size (* scale (:size op))))
op)))
(defprotocol IActivePalette
(active-palette [this]
"The palette selected by this resolver's most recently resolved frame."))
(defn resolver
"Resolve an output frame, selecting native content at each symbol boundary.
Every instance owns its cursors and buffers. The IResolver queries return
native node frames and world matrices for the last rendered output frame."
[clip sid store palette opts]
(let [context? (and (map? palette) (:palettes palette) (:offsets palette))]
(let [context? (and (map? palette) (:palettes palette) (:offsets palette))
active-palette-state (atom (:default palette))]
(letfn [(channel-value [selection frame]
(cond
(nil? selection) nil
@ -323,6 +328,9 @@
(root-selection-at sym (js/Math.floor f) inherited)
inherited)
(:default palette))))
;; The same decision that maps local drawing slots to
;; the active bank also names the clear colour.
(when root? (reset! active-palette-state @active))
(vreset! entered {})
(let [by-id (into {} (map (juxt :node identity))
(own (js/Math.floor f) (js/Math.floor pre)))]
@ -409,7 +417,9 @@
symbol/IResolver
(world-of [_ path] (symbol/world-of r path))
(frame-of [_ path] (symbol/frame-of r path))
(pre-frame-of [_ path] (symbol/pre-frame-of r path)))))))
(pre-frame-of [_ path] (symbol/pre-frame-of r path))
IActivePalette
(active-palette [_] @active-palette-state))))))
(defn center
"The middle of everything symbol `sid` draws, over all its frames, in its own

View file

@ -109,6 +109,16 @@
i (and p (slot-index p value))]
(if (some? i) (+ (get offsets id 0) i) 255)))
(defn background-index
"The raster index of slot zero in the resolver's active palette.
`index-of` remains a supported legacy palette map for domain callers; it has
no palette banks or active selection, so its established `:bg` index applies."
[palette active]
(if (and (:palettes palette) (:offsets palette))
(render-index palette active 0)
(get palette :bg 0)))
(defn valid-palette? [{:keys [id name slots]}]
(and id (string? name) (seq name) (vector? slots) (pos? (count slots))
(<= (count slots) 255)

View file

@ -34,6 +34,7 @@
(:refer-clojure :exclude [run!])
(:require [arthur.audio.mix :as mix]
[arthur.domain.clip :as clip]
[arthur.domain.palette :as pal]
[arthur.domain.raster :as raster]))
(defprotocol Exporter
@ -191,8 +192,7 @@
;; how.
doc (assoc-in clip [:symbols sid] sym)
resolve-frame (clip/resolver doc sid store palette nil)
ras (raster/make width height)
bg (get palette :bg 0)]
ras (raster/make width height)]
(-> (audio! doc sid store audio-url)
(.then (fn [audio]
(js/Promise.resolve
@ -210,9 +210,11 @@
(fn [chain i]
(.then chain
(fn [_]
(let [ops (resolve-frame i)]
(-> ras
(raster/clear! bg)
(raster/draw-ops! (resolve-frame i)))
(raster/clear! (pal/background-index palette
(clip/active-palette resolve-frame)))
(raster/draw-ops! ops)))
(-> (js/Promise.resolve (frame! exporter i ras))
(.then (fn [_]
(when on-progress

View file

@ -194,7 +194,9 @@
symbol/IResolver
(world-of [_ path] (symbol/world-of resolve path))
(frame-of [_ path] (symbol/frame-of resolve path))
(pre-frame-of [_ path] (symbol/pre-frame-of resolve path)))))))
(pre-frame-of [_ path] (symbol/pre-frame-of resolve path))
clip/IActivePalette
(active-palette [_] (clip/active-palette resolve)))))))
(rf/reg-sub
::underlay

View file

@ -19,6 +19,8 @@
times a second at a 30fps clip on a 60Hz display, not sixty — and it carries
no global interceptors."
(:require [arthur.clock :as clock]
[arthur.domain.clip :as clip]
[arthur.domain.palette :as pal]
[arthur.domain.pick :as pick]
[arthur.domain.raster :as raster]
[arthur.events.playback :as pb]
@ -171,10 +173,11 @@
;; independent of the footage, so the size the frame is rasterised at comes
;; out of the document like everything else.
(let [ras (raster-for width height)]
(let [ops (resolver f)]
(let [ops (resolver f)
bg (pal/background-index palette (clip/active-palette resolver))]
(swap! state assoc :ops ops)
(-> ras
(raster/clear! (get palette :bg 0))
(raster/clear! bg)
(raster/draw-ops! ops)))
(js/performance.mark "arthur/blit:start")
(canvas/blit! canvas ras ramp)

View file

@ -98,10 +98,11 @@
(when (clip/symbol document sid)
(let [resolve (clip/resolver document sid store palette {})
[w h] (clip/stage document sid)]
(let [ops (resolve 0)]
(-> (raster/make w h)
(raster/clear! (get palette :bg 0))
(raster/draw-ops! (resolve 0))
(canvas/->png ramp))))
(raster/clear! (pal/background-index palette (clip/active-palette resolve)))
(raster/draw-ops! ops)
(canvas/->png ramp)))))
(catch :default _ nil)))
(defn- symbol-thumb

View file

@ -1083,12 +1083,12 @@
(if (= drop-lane (:select row))
(update row :cels (fnil conj []) lane-ghost)
row))))
;; The palette track is a permanent master row. An
;; untargeted visual drop becomes a new ordinary row and
;; therefore previews immediately BELOW it, where the
;; committed row will appear—not above the master row.
;; An untargeted visual drop becomes a new ordinary row
;; at the bottom. Inserting it above the lanes shifts the
;; lane under the pointer and makes the browser alternate
;; between this ghost and that lane's drop target.
(and ghost (not (:sound? drop)))
((fn [rows] (into [(first rows) ghost] (rest rows)))))
(fn [rows] (conj (vec rows) ghost)))
sounds (cond->> (into (vec (filter :sound? picture))
(sound-rows clip open expanded))
(and ghost (:sound? drop)) (cons ghost))

View file

@ -354,4 +354,11 @@
(is (= (pal/render-index context :day 1) (color main 2))
"a nil track interval is a real gap and restores the authoring palette")
(is (= (pal/render-index context :night 1) (color drawing 0))
"the same nested symbol uses its authoring palette when viewed directly")))
"the same nested symbol uses its authoring palette when viewed directly")
;; The renderer clears with this exact resolver state before rasterising the
;; returned ops, so a palette cut changes the stage background as well as
;; authored shapes.
(main 1)
(is (= :night (clip/active-palette main)))
(is (= (pal/render-index context :night 0)
(pal/background-index context (clip/active-palette main))))))