idk
This commit is contained in:
parent
edca82cd5d
commit
664252e0fc
17 changed files with 373 additions and 112 deletions
|
|
@ -360,9 +360,15 @@
|
||||||
(if (and (= :linear (segment-interp ch left)) right (>= f left) (> right left))
|
(if (and (= :linear (segment-interp ch left)) right (>= f left) (> right left))
|
||||||
(let [b (get (:keys ch) right)
|
(let [b (get (:keys ch) right)
|
||||||
t (/ (- f left) (- right left))]
|
t (/ (- f left) (- right left))]
|
||||||
(if (vector? a)
|
(cond
|
||||||
|
;; Palette-choice channels interpolate identities into a blend
|
||||||
|
;; descriptor. The renderer keeps indexed geometry in the left
|
||||||
|
;; palette's bank and blends that bank's ramp toward the right one.
|
||||||
|
(and (= :palette (:semantic ch)) (keyword? a) (keyword? b))
|
||||||
|
{:from a :to b :t t}
|
||||||
|
(vector? a)
|
||||||
(mapv (fn [x y] (+ x (* t (- y x)))) a b)
|
(mapv (fn [x y] (+ x (* t (- y x)))) a b)
|
||||||
(+ a (* t (- b a)))))
|
:else (+ a (* t (- b a)))))
|
||||||
a)))
|
a)))
|
||||||
|
|
||||||
(defn- keyed-at
|
(defn- keyed-at
|
||||||
|
|
@ -514,6 +520,7 @@
|
||||||
(let [values (when (map? (:keys ch)) (vals (:keys ch)))
|
(let [values (when (map? (:keys ch)) (vals (:keys ch)))
|
||||||
first-value (first values)
|
first-value (first values)
|
||||||
linear-values? (or (every? number? values)
|
linear-values? (or (every? number? values)
|
||||||
|
(and (= :palette (:semantic ch)) (every? keyword? values))
|
||||||
(and (vector? first-value)
|
(and (vector? first-value)
|
||||||
(pos? (count first-value))
|
(pos? (count first-value))
|
||||||
(every? (fn [v] (and (vector? v)
|
(every? (fn [v] (and (vector? v)
|
||||||
|
|
|
||||||
|
|
@ -280,9 +280,14 @@
|
||||||
(and a (<= a frame) (< frame b))))
|
(and a (<= a frame) (< frame b))))
|
||||||
(symbol/children (:nodes track-symbol))))
|
(symbol/children (:nodes track-symbol))))
|
||||||
palette-symbol (some-> palette-clip node/source
|
palette-symbol (some-> palette-clip node/source
|
||||||
(#(symbol clip %)))]
|
(#(symbol clip %)))
|
||||||
(when (= :palette (:type palette-symbol))
|
fallback (when (= :palette (:type palette-symbol))
|
||||||
(:palette-ref palette-symbol))))
|
(:palette-ref palette-symbol))
|
||||||
|
choice (get-in palette-clip [:channels [:palette]])
|
||||||
|
start (some-> palette-clip node/placed-span first)]
|
||||||
|
(or (when (and choice start)
|
||||||
|
(channel-value choice (- frame start)))
|
||||||
|
fallback)))
|
||||||
track-value
|
track-value
|
||||||
(channel-value (:palette owner) frame)
|
(channel-value (:palette owner) frame)
|
||||||
inherited
|
inherited
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,10 @@
|
||||||
change to this spec silently change what gets drawn."
|
change to this spec silently change what gets drawn."
|
||||||
(let [base (into #{[:vis]} xform-paths)]
|
(let [base (into #{[:vis]} xform-paths)]
|
||||||
{:group base
|
{:group base
|
||||||
:instance base
|
;; A palette-track placement is still an instance. Its palette choice is
|
||||||
|
;; a parameter channel on that instance, so the generic span commands can
|
||||||
|
;; move and trim it without knowing what kind of lane owns it.
|
||||||
|
:instance (conj base [:palette])
|
||||||
;; A sound is not in the picture: no transform, no visibility. After
|
;; A sound is not in the picture: no transform, no visibility. After
|
||||||
;; Effects' audio-only layer has no Transform group for the same reason.
|
;; Effects' audio-only layer has no Transform group for the same reason.
|
||||||
:audio #{[:audio :gain] [:audio :pan] [:audio :rate]}
|
:audio #{[:audio :gain] [:audio :pan] [:audio :rate]}
|
||||||
|
|
@ -102,7 +105,9 @@
|
||||||
[n path f v]
|
[n path f v]
|
||||||
(let [c (get (channels n) path)]
|
(let [c (get (channels n) path)]
|
||||||
(assoc-in n [:channels path]
|
(assoc-in n [:channels path]
|
||||||
(if (:keys c) (assoc-in c [:keys f] v) (ch/framed v)))))
|
(if (:keys c)
|
||||||
|
(assoc-in c [:keys f] v)
|
||||||
|
(merge (select-keys c [:semantic]) (ch/framed v))))))
|
||||||
|
|
||||||
(defn set-keyed-channel
|
(defn set-keyed-channel
|
||||||
"Write `v` as a key at `f`, starting an animated channel when needed. This is
|
"Write `v` as a key at `f`, starting an animated channel when needed. This is
|
||||||
|
|
@ -113,7 +118,8 @@
|
||||||
(assoc-in n [:channels path]
|
(assoc-in n [:channels path]
|
||||||
(if (:keys c)
|
(if (:keys c)
|
||||||
(assoc-in c [:keys f] v)
|
(assoc-in c [:keys f] v)
|
||||||
(ch/keyed {f v} (if (boolean? v) :hold :linear))))))
|
(merge (select-keys c [:semantic])
|
||||||
|
(ch/keyed {f v} (if (or (boolean? v) (keyword? v)) :hold :linear)))))))
|
||||||
|
|
||||||
(defn toggle-key
|
(defn toggle-key
|
||||||
"Key channel `path` on the node's own frame `f` with the value it has there, or
|
"Key channel `path` on the node's own frame `f` with the value it has there, or
|
||||||
|
|
@ -128,7 +134,8 @@
|
||||||
ks (dissoc (:keys c) f)]
|
ks (dissoc (:keys c) f)]
|
||||||
(assoc-in n [:channels path]
|
(assoc-in n [:channels path]
|
||||||
(cond
|
(cond
|
||||||
(not (:keys c)) (ch/keyed {f v} (if (boolean? v) :hold :linear))
|
(not (:keys c)) (merge (select-keys c [:semantic])
|
||||||
|
(ch/keyed {f v} (if (or (boolean? v) (keyword? v)) :hold :linear)))
|
||||||
(not (contains? (:keys c) f)) (assoc-in c [:keys f] v)
|
(not (contains? (:keys c) f)) (assoc-in c [:keys f] v)
|
||||||
(seq ks) (cond-> (assoc c :keys ks)
|
(seq ks) (cond-> (assoc c :keys ks)
|
||||||
(:segments c) (update :segments dissoc f))
|
(:segments c) (update :segments dissoc f))
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,10 @@
|
||||||
(vals (:symbols clip)))
|
(vals (:symbols clip)))
|
||||||
(for [sym (vals (:symbols clip))
|
(for [sym (vals (:symbols clip))
|
||||||
n (vals (:nodes sym))]
|
n (vals (:nodes sym))]
|
||||||
(:palette n))))
|
(:palette n))
|
||||||
|
(for [sym (vals (:symbols clip))
|
||||||
|
n (vals (:nodes sym))]
|
||||||
|
(get-in n [:channels [:palette]]))))
|
||||||
ps (select-keys (palettes clip) used)
|
ps (select-keys (palettes clip) used)
|
||||||
ordered (sort-by (comp str key) ps)
|
ordered (sort-by (comp str key) ps)
|
||||||
offsets (loop [xs ordered at 0 out {}]
|
offsets (loop [xs ordered at 0 out {}]
|
||||||
|
|
@ -105,7 +108,8 @@
|
||||||
(defn render-index
|
(defn render-index
|
||||||
"A local slot (or a legacy tone keyword) in palette `id` -> raster index."
|
"A local slot (or a legacy tone keyword) in palette `id` -> raster index."
|
||||||
[{:keys [palettes offsets]} id value]
|
[{:keys [palettes offsets]} id value]
|
||||||
(let [p (get palettes id)
|
(let [id (if (map? id) (:from id) id)
|
||||||
|
p (get palettes id)
|
||||||
i (and p (slot-index p value))]
|
i (and p (slot-index p value))]
|
||||||
(if (some? i) (+ (get offsets id 0) i) 255)))
|
(if (some? i) (+ (get offsets id 0) i) 255)))
|
||||||
|
|
||||||
|
|
@ -119,6 +123,26 @@
|
||||||
(render-index palette active 0)
|
(render-index palette active 0)
|
||||||
(get palette :bg 0)))
|
(get palette :bg 0)))
|
||||||
|
|
||||||
|
(defn effective-ramp
|
||||||
|
"The compiled ramp with the active palette blend applied. A hold returns the
|
||||||
|
original vector; a linear palette segment rewrites only the left palette's
|
||||||
|
bank, which is the bank the indexed raster was drawn into."
|
||||||
|
[{:keys [palettes offsets ramp]} active]
|
||||||
|
(if-let [{:keys [from to t]} (when (map? active) active)]
|
||||||
|
(let [a (get palettes from)
|
||||||
|
b (get palettes to)
|
||||||
|
at (get offsets from)
|
||||||
|
n (min (count (:slots a)) (count (:slots b)))]
|
||||||
|
(if (and a b (number? at) (number? t))
|
||||||
|
(reduce (fn [out i]
|
||||||
|
(let [x (hex->rgb (get-in a [:slots i :hex]))
|
||||||
|
y (hex->rgb (get-in b [:slots i :hex]))]
|
||||||
|
(assoc out (+ at i)
|
||||||
|
(mapv #(js/Math.round (+ %1 (* t (- %2 %1)))) x y))))
|
||||||
|
ramp (range n))
|
||||||
|
ramp))
|
||||||
|
ramp))
|
||||||
|
|
||||||
(defn valid-palette? [{:keys [id name slots]}]
|
(defn valid-palette? [{:keys [id name slots]}]
|
||||||
(and id (string? name) (seq name) (vector? slots) (pos? (count slots))
|
(and id (string? name) (seq name) (vector? slots) (pos? (count slots))
|
||||||
(<= (count slots) 255)
|
(<= (count slots) 255)
|
||||||
|
|
|
||||||
|
|
@ -751,7 +751,12 @@
|
||||||
:frames 1 :fps (clip/fps document root-sid) :nodes {}}))
|
:frames 1 :fps (clip/fps document root-sid) :nodes {}}))
|
||||||
id (random-uuid)
|
id (random-uuid)
|
||||||
result (span/place-symbol document st track-id id palette-sid frame
|
result (span/place-symbol document st track-id id palette-sid frame
|
||||||
{:extent :grow-symbol :remainder-id (random-uuid)})]
|
{:extent :grow-symbol :remainder-id (random-uuid)})
|
||||||
|
result (if-let [placed (:clip result)]
|
||||||
|
(assoc result :clip
|
||||||
|
(assoc-in placed [:symbols track-id :nodes id :channels [:palette]]
|
||||||
|
(assoc (ch/framed palette-id) :semantic :palette)))
|
||||||
|
result)]
|
||||||
(if-let [why (:refused result)]
|
(if-let [why (:refused result)]
|
||||||
(update db :project merge {:status why})
|
(update db :project merge {:status why})
|
||||||
(-> db
|
(-> db
|
||||||
|
|
@ -765,6 +770,34 @@
|
||||||
(let [put (if (get-in db [:ui :auto-key?]) node/set-keyed-channel node/set-channel)]
|
(let [put (if (get-in db [:ui :auto-key?]) node/set-keyed-channel node/set-channel)]
|
||||||
(edit/edit db #(update-in % [:symbols sid :nodes id] put path frame value)))))
|
(edit/edit db #(update-in % [:symbols sid :nodes id] put path frame value)))))
|
||||||
|
|
||||||
|
(defn- seed-palette-choice [document sid id]
|
||||||
|
(if (get-in document [:symbols sid :nodes id :channels [:palette]])
|
||||||
|
document
|
||||||
|
(let [source (get-in document [:symbols sid :nodes id :source :symbol])
|
||||||
|
palette-id (get-in document [:symbols source :palette-ref])]
|
||||||
|
(assoc-in document [:symbols sid :nodes id :channels [:palette]]
|
||||||
|
(assoc (ch/framed palette-id) :semantic :palette)))))
|
||||||
|
|
||||||
|
(rf/reg-event-db
|
||||||
|
::set-palette-choice
|
||||||
|
(fn [db [_ sid id frame palette-id]]
|
||||||
|
(let [put (if (get-in db [:ui :auto-key?]) node/set-keyed-channel node/set-channel)]
|
||||||
|
(edit/edit db
|
||||||
|
(fn [document]
|
||||||
|
(update-in (seed-palette-choice document sid id)
|
||||||
|
[:symbols sid :nodes id]
|
||||||
|
put [:palette] frame palette-id))))))
|
||||||
|
|
||||||
|
(rf/reg-event-db
|
||||||
|
::toggle-palette-key
|
||||||
|
(fn [db [_ sid id frame]]
|
||||||
|
(let [st (:store (store/entry (:clip/current db)))]
|
||||||
|
(edit/edit db
|
||||||
|
(fn [document]
|
||||||
|
(update-in (seed-palette-choice document sid id)
|
||||||
|
[:symbols sid :nodes id]
|
||||||
|
node/toggle-key [:palette] frame st))))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::set-channels
|
::set-channels
|
||||||
;; One property assignment over a stage selection is one document edit.
|
;; One property assignment over a stage selection is one document edit.
|
||||||
|
|
|
||||||
|
|
@ -63,26 +63,16 @@
|
||||||
(defn selected
|
(defn selected
|
||||||
"`db` with `selection` selected, and nothing aimed.
|
"`db` with `selection` selected, and nothing aimed.
|
||||||
|
|
||||||
The rows above a selection are opened, so one made deep on the stage is seen
|
Selection and row disclosure are independent. A click or trim can select a
|
||||||
in the timeline. Not a sound's: its row is always in the audio section, and
|
nested item, but only the row's twist button opens or closes it."
|
||||||
opening the placement it is heard through would bury it.
|
|
||||||
|
|
||||||
AN EMPTY PATH IS A REAL SELECTION, not a missing one. The open symbol's own
|
|
||||||
lane row names itself with `[]`, and `(pop [])` THROWS — so clicking that row
|
|
||||||
aborted the whole event: nothing was selected, nothing was aimed, and the next
|
|
||||||
polygon went wherever the stale target still pointed, which is most of what
|
|
||||||
made aiming a lane feel random. There is nothing above a selection at the top
|
|
||||||
to open, so the guard is `seq` rather than `some?`."
|
|
||||||
[db selection]
|
[db selection]
|
||||||
(let [[kind sid id path] selection
|
;; A captured trim gesture also selects on pointer-up. It must not acquire the
|
||||||
sound? (= :audio (get-in (store/entry (:clip/current db))
|
;; unrelated side effect of opening every row above the edited clip.
|
||||||
[:clip :symbols sid :nodes id :kind]))]
|
(cond-> (-> db
|
||||||
(cond-> (-> db
|
(assoc-in [:ui :selection] selection)
|
||||||
(assoc-in [:ui :selection] selection)
|
(assoc-in [:ui :selections] (if selection [selection] []))
|
||||||
(assoc-in [:ui :selections] (if selection [selection] []))
|
(update :ui dissoc :points :retry))
|
||||||
(update :ui dissoc :points :retry))
|
(nil? selection) (assoc-in [:ui :target] nil)))
|
||||||
(and (= :node kind) (seq path) (not sound?))
|
|
||||||
(update-in [:ui :expanded] (fnil into #{}) (rest (reductions conj [] (pop path)))))))
|
|
||||||
|
|
||||||
(defn target-of
|
(defn target-of
|
||||||
"The target `selection` names: `{:sid :id :path}`, or nil for the open symbol
|
"The target `selection` names: `{:sid :id :path}`, or nil for the open symbol
|
||||||
|
|
@ -487,8 +477,14 @@
|
||||||
question `symbol/lane?` answers."
|
question `symbol/lane?` answers."
|
||||||
[clip st db frame]
|
[clip st db frame]
|
||||||
(let [open (get-in db [:ui :open])
|
(let [open (get-in db [:ui :open])
|
||||||
down (where-new-goes clip db)]
|
down (where-new-goes clip db)
|
||||||
(assoc (nest/inside clip st open down frame) :path down)))
|
inside (nest/inside clip st open down frame)]
|
||||||
|
;; A stale or presently invisible target is no target. Creation must remain
|
||||||
|
;; a total operation, so it falls back to the document's main symbol.
|
||||||
|
(if (:sid inside)
|
||||||
|
(assoc inside :path down)
|
||||||
|
{:sid (if (get-in clip [:symbols :main]) :main open)
|
||||||
|
:path [] :frame frame})))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; clipboard and local duplication
|
;; clipboard and local duplication
|
||||||
|
|
@ -562,7 +558,7 @@
|
||||||
{:sid open :path [] :frame frame}
|
{:sid open :path [] :frame frame}
|
||||||
|
|
||||||
(nil? n)
|
(nil? n)
|
||||||
{:refused "the paste target no longer exists"}
|
{:sid (if (get-in clip [:symbols :main]) :main open) :path [] :frame frame}
|
||||||
|
|
||||||
(= :instance (:kind n))
|
(= :instance (:kind n))
|
||||||
(let [visible (nest/inside clip st open path frame)
|
(let [visible (nest/inside clip st open path frame)
|
||||||
|
|
@ -576,12 +572,12 @@
|
||||||
at (or (:frame visible) (:frame shown) mapped held)]
|
at (or (:frame visible) (:frame shown) mapped held)]
|
||||||
(if (number? at)
|
(if (number? at)
|
||||||
{:sid (node/source n) :path (vec path) :frame (js/Math.floor at)}
|
{:sid (node/source n) :path (vec path) :frame (js/Math.floor at)}
|
||||||
{:refused "the playhead has no single frame inside the paste target"}))
|
{:sid (if (get-in clip [:symbols :main]) :main open) :path [] :frame frame}))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(if (number? owner-frame)
|
(if (number? owner-frame)
|
||||||
{:sid sid :path (vec (butlast path)) :frame (js/Math.floor owner-frame)}
|
{:sid sid :path (vec (butlast path)) :frame (js/Math.floor owner-frame)}
|
||||||
{:refused "the playhead has no single frame in the paste target"}))))
|
{:sid (if (get-in clip [:symbols :main]) :main open) :path [] :frame frame}))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::paste
|
::paste
|
||||||
|
|
|
||||||
|
|
@ -210,16 +210,19 @@
|
||||||
(fn [chain i]
|
(fn [chain i]
|
||||||
(.then chain
|
(.then chain
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(let [ops (resolve-frame i)]
|
(let [ops (resolve-frame i)
|
||||||
|
active (clip/active-palette resolve-frame)]
|
||||||
(-> ras
|
(-> ras
|
||||||
(raster/clear! (pal/background-index palette
|
(raster/clear! (pal/background-index palette active))
|
||||||
(clip/active-palette resolve-frame)))
|
(raster/draw-ops! ops))
|
||||||
(raster/draw-ops! ops)))
|
(-> (js/Promise.resolve
|
||||||
(-> (js/Promise.resolve (frame! exporter i ras))
|
(frame! exporter i
|
||||||
|
(assoc ras :palette-ramp
|
||||||
|
(pal/effective-ramp palette active))))
|
||||||
(.then (fn [_]
|
(.then (fn [_]
|
||||||
(when on-progress
|
(when on-progress
|
||||||
(on-progress (inc i) frames))
|
(on-progress (inc i) frames))
|
||||||
(yield!)))))))
|
(yield!))))))))
|
||||||
(js/Promise.resolve)
|
(js/Promise.resolve)
|
||||||
(range frames))))
|
(range frames))))
|
||||||
(.then (fn [_] (finish! exporter)))))))
|
(.then (fn [_] (finish! exporter)))))))
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
;; raster: keeping a reference to it and encoding later would encode
|
;; raster: keeping a reference to it and encoding later would encode
|
||||||
;; the last frame N times, and every frame would be a valid PNG of the
|
;; the last frame N times, and every frame would be a valid PNG of the
|
||||||
;; wrong picture.
|
;; wrong picture.
|
||||||
(-> (encode raster ramp)
|
(-> (encode raster (or (:palette-ramp raster) ramp))
|
||||||
(.then (fn [bytes]
|
(.then (fn [bytes]
|
||||||
(swap! state update :entries conj
|
(swap! state update :entries conj
|
||||||
{:name (str name "/" (pad (inc i) digits) ".png")
|
{:name (str name "/" (pad (inc i) digits) ".png")
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,13 @@
|
||||||
:parse #(if (seq %) (js/parseInt % 10) nil)
|
:parse #(if (seq %) (js/parseInt % 10) nil)
|
||||||
:on-number on-change}]])
|
:on-number on-change}]])
|
||||||
|
|
||||||
|
(defn- name-field [label value commit!]
|
||||||
|
[:label.inspector-field label
|
||||||
|
[:input {:type "text" :default-value value
|
||||||
|
:key value
|
||||||
|
:on-key-down #(when (= "Enter" (.-key %)) (.. % -target blur))
|
||||||
|
:on-blur #(commit! (.. % -target -value))}]])
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; the clip
|
;; the clip
|
||||||
|
|
||||||
|
|
@ -89,12 +96,13 @@
|
||||||
busy? (:busy? project)]
|
busy? (:busy? project)]
|
||||||
[section "project"
|
[section "project"
|
||||||
[facts
|
[facts
|
||||||
"name" (or (:name project) (:name clip))
|
|
||||||
"stage" (str (:width clip) "×" (:height clip))
|
"stage" (str (:width clip) "×" (:height clip))
|
||||||
"open" (some-> open name)
|
"open" (some-> open name)
|
||||||
"length" (str frames " frames")
|
"length" (str frames " frames")
|
||||||
"rate" (str fps " fps")]
|
"rate" (str fps " fps")]
|
||||||
[:div.inspector-form
|
[:div.inspector-form
|
||||||
|
[name-field "name" (or (:name project) (:name clip) "")
|
||||||
|
#(rf/dispatch [::project/rename %])]
|
||||||
[number-field "width" (:width clip)
|
[number-field "width" (:width clip)
|
||||||
#(rf/dispatch [::project/project-setting :width %]) nil busy?]
|
#(rf/dispatch [::project/project-setting :width %]) nil busy?]
|
||||||
[number-field "height" (:height clip)
|
[number-field "height" (:height clip)
|
||||||
|
|
@ -208,7 +216,6 @@
|
||||||
frame (:frame local)]
|
frame (:frame local)]
|
||||||
[section (str (name (:kind n)) " · in " (name sid))
|
[section (str (name (:kind n)) " · in " (name sid))
|
||||||
[facts
|
[facts
|
||||||
"name" (or (:name n) (brief id))
|
|
||||||
"id" (brief id)
|
"id" (brief id)
|
||||||
;; Which symbol a cel places. The one fact that makes an instance
|
;; Which symbol a cel places. The one fact that makes an instance
|
||||||
;; legible as an instance rather than as a node.
|
;; legible as an instance rather than as a node.
|
||||||
|
|
@ -217,6 +224,9 @@
|
||||||
;; in this symbol. See `node/placed-span`.
|
;; in this symbol. See `node/placed-span`.
|
||||||
"span" (when start (str start " … " end))
|
"span" (when start (str start " … " end))
|
||||||
"at" (when (node/mapped-time? n) (str (get-in n [:time :at] 0)))]
|
"at" (when (node/mapped-time? n) (str (get-in n [:time :at] 0)))]
|
||||||
|
[:div.inspector-form
|
||||||
|
[name-field "name" (or (:name n) "")
|
||||||
|
#(rf/dispatch [::ui/rename-node sid id %])]]
|
||||||
(when (:paint? n) [drawing-keys sid id n @(rf/subscribe [::sub/selected-local])])
|
(when (:paint? n) [drawing-keys sid id n @(rf/subscribe [::sub/selected-local])])
|
||||||
[:div.row {:style {:margin-top "6px"}} [:span.dim "channels"]]
|
[:div.row {:style {:margin-top "6px"}} [:span.dim "channels"]]
|
||||||
(let [{:keys [frame]} local]
|
(let [{:keys [frame]} local]
|
||||||
|
|
@ -230,6 +240,58 @@
|
||||||
[channel-control sid id path ch frame auto-key?]
|
[channel-control sid id path ch frame auto-key?]
|
||||||
[:dd (channel-state ch)])]))])]))
|
[:dd (channel-state ch)])]))])]))
|
||||||
|
|
||||||
|
(defn- palette-placement-section [[sid id n]]
|
||||||
|
(let [clip @(rf/subscribe [::render/clip])
|
||||||
|
transition (node/source n)
|
||||||
|
open-frame @(rf/subscribe [::render/open-frame])
|
||||||
|
[start _] (node/placed-span n)
|
||||||
|
frame (max 0 (- open-frame (or start 0)))
|
||||||
|
ch (or (get-in n [:channels [:palette]])
|
||||||
|
(assoc (channel/framed
|
||||||
|
(get-in clip [:symbols (node/source n) :palette-ref]))
|
||||||
|
:semantic :palette))
|
||||||
|
value (channel/value-at ch frame nil)
|
||||||
|
chosen (if (map? value) (:from value) value)
|
||||||
|
keyframes (sort (keys (:keys ch)))
|
||||||
|
left (when (seq keyframes)
|
||||||
|
(or (last (filter #(<= % frame) keyframes)) (first keyframes)))
|
||||||
|
right (when left (first (filter #(< left %) keyframes)))
|
||||||
|
palettes (sort-by (comp str :name val) (pal/palettes clip))]
|
||||||
|
[section "palette transition"
|
||||||
|
[:div.inspector-form
|
||||||
|
[name-field "name" (clip-domain/symbol-name clip transition)
|
||||||
|
#(rf/dispatch [::project/rename-symbol transition %])]]
|
||||||
|
[:div.inspector-form
|
||||||
|
[:label.inspector-field "palette"
|
||||||
|
[:span.row
|
||||||
|
[:button.key {:class (cond (contains? (:keys ch) frame) "on" (:keys ch) "keyed")
|
||||||
|
:title (if (:keys ch) (str (count (:keys ch)) " keys") "key this here")
|
||||||
|
:on-click #(rf/dispatch [::project/toggle-palette-key sid id frame])}
|
||||||
|
"◆"]
|
||||||
|
[:select {:value (str chosen)
|
||||||
|
:on-change
|
||||||
|
(fn [e]
|
||||||
|
(let [v (.. e -target -value)
|
||||||
|
palette-id (first (filter #(= v (str %)) (keys (pal/palettes clip))))]
|
||||||
|
(rf/dispatch [::project/set-palette-choice sid id frame palette-id])))}
|
||||||
|
(for [[palette-id p] palettes]
|
||||||
|
^{:key (str palette-id)}
|
||||||
|
[:option {:value (str palette-id)} (:name p)])]]]
|
||||||
|
(when right
|
||||||
|
[:label.inspector-field (str "key " left " → " right)
|
||||||
|
[segment-select sid id [:palette] ch left]])]]))
|
||||||
|
|
||||||
|
(defn- palette-symbol-section [sid]
|
||||||
|
(let [clip @(rf/subscribe [::render/clip])
|
||||||
|
sym (get-in clip [:symbols sid])]
|
||||||
|
[section "palette transition"
|
||||||
|
[:div.inspector-form
|
||||||
|
[name-field "name" (clip-domain/symbol-name clip sid)
|
||||||
|
#(rf/dispatch [::project/rename-symbol sid %])]]
|
||||||
|
[facts "palette" (or (get-in clip [:palettes (:palette-ref sym) :name])
|
||||||
|
(:palette-ref sym))]
|
||||||
|
[:div.dim "Keyframes and cut/tween interpolation are edited on a placement in the palette lane."]]))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; corrections
|
;; corrections
|
||||||
|
|
||||||
|
|
@ -540,6 +602,8 @@
|
||||||
"stage" (let [[w h] (clip-domain/stage clip sid)] (str w "×" h))
|
"stage" (let [[w h] (clip-domain/stage clip sid)] (str w "×" h))
|
||||||
"nodes" (str (count (:nodes sym)))]
|
"nodes" (str (count (:nodes sym)))]
|
||||||
[:div.inspector-form
|
[:div.inspector-form
|
||||||
|
[name-field "name" (clip-domain/symbol-name clip sid)
|
||||||
|
#(rf/dispatch [::project/rename-symbol sid %])]
|
||||||
[number-field "length (frames)" (:frames sym)
|
[number-field "length (frames)" (:frames sym)
|
||||||
#(rf/dispatch [::project/symbol-setting sid :frames %]) nil busy?]
|
#(rf/dispatch [::project/symbol-setting sid :frames %]) nil busy?]
|
||||||
[number-field "fps" (clip-domain/fps clip sid)
|
[number-field "fps" (clip-domain/fps clip sid)
|
||||||
|
|
@ -567,28 +631,6 @@
|
||||||
(rf/dispatch [::project/symbol-setting sid :height nil]))}
|
(rf/dispatch [::project/symbol-setting sid :height nil]))}
|
||||||
"use project stage"]])]]))
|
"use project stage"]])]]))
|
||||||
|
|
||||||
(defn- symbol-parameters-section [sid frame title]
|
|
||||||
(let [clip @(rf/subscribe [::render/clip])
|
|
||||||
auto-key? @(rf/subscribe [::sub/auto-key?])
|
|
||||||
nodes (get-in clip [:symbols sid :nodes])]
|
|
||||||
(when (and sid (number? frame) (seq nodes))
|
|
||||||
(into
|
|
||||||
[section title]
|
|
||||||
(for [[id n] (sort-by (comp str key) nodes)
|
|
||||||
:let [editable (filter (fn [[path ch]]
|
|
||||||
(and (contains? (node/defaults-of n) path)
|
|
||||||
(not (:dense ch))))
|
|
||||||
(node/channels n))]
|
|
||||||
:when (seq editable)]
|
|
||||||
^{:key (str id)}
|
|
||||||
(into
|
|
||||||
[:div.inspector-form
|
|
||||||
[:div.row [:span.dim (or (:name n) (brief id))]]]
|
|
||||||
(for [[path ch] editable]
|
|
||||||
^{:key (str path)}
|
|
||||||
[:label.inspector-field (str/join " " (map name path))
|
|
||||||
[channel-control sid id path ch frame auto-key?]])))))))
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; tracked objects
|
;; tracked objects
|
||||||
;;
|
;;
|
||||||
|
|
@ -692,6 +734,10 @@
|
||||||
;; lane traces is not a question with one answer, and naming the drawing
|
;; lane traces is not a question with one answer, and naming the drawing
|
||||||
;; showing now would move the section under the playhead.
|
;; showing now would move the section under the playhead.
|
||||||
placed (node/source (peek node))
|
placed (node/source (peek node))
|
||||||
|
palette-placement? (and node
|
||||||
|
(= :palette (get-in clip [:symbols placed :type])))
|
||||||
|
palette-symbol? (and (= :symbol (first selection))
|
||||||
|
(= :palette (get-in clip [:symbols (second selection) :type])))
|
||||||
face (or placed (when (trace/traceable? clip open) open))
|
face (or placed (when (trace/traceable? clip open) open))
|
||||||
faces (when face (trace/faces clip face))
|
faces (when face (trace/faces clip face))
|
||||||
;; Every face the open symbol has, which is what the footage switch is
|
;; Every face the open symbol has, which is what the footage switch is
|
||||||
|
|
@ -706,23 +752,19 @@
|
||||||
[:section.pane.params
|
[:section.pane.params
|
||||||
[:div.pane-head "inspector"]
|
[:div.pane-head "inspector"]
|
||||||
[:div {:style {:min-height 0}}
|
[:div {:style {:min-height 0}}
|
||||||
[clip-section]
|
(if palette-placement?
|
||||||
(when node [node-section node])
|
[palette-placement-section node]
|
||||||
(when placed [symbol-section placed "source symbol"])
|
[clip-section])
|
||||||
(when placed
|
(when (and node (not palette-placement?)) [node-section node])
|
||||||
(let [{source-sid :sid source-frame :frame} @(rf/subscribe [::sub/selected-local])]
|
(when (and placed (not palette-placement?)) [symbol-section placed "source symbol"])
|
||||||
(when (= placed source-sid)
|
(when (and node (not palette-placement?)) ^{:key (str (first node) "/" (second node))}
|
||||||
[symbol-parameters-section placed source-frame "source parameters"])))
|
|
||||||
(when node ^{:key (str (first node) "/" (second node))}
|
|
||||||
[correction-section node])
|
[correction-section node])
|
||||||
(when (seq here) [footage-section here])
|
(when (and (not palette-placement?) (seq here)) [footage-section here])
|
||||||
(when (and face (or (trace/traceable? clip face) (seq faces)))
|
(when (and face (or (trace/traceable? clip face) (seq faces)))
|
||||||
[tracing-section face faces path])
|
[tracing-section face faces path])
|
||||||
(when face ^{:key (str "perf/" face)} [performance-section face])
|
(when face ^{:key (str "perf/" face)} [performance-section face])
|
||||||
(when (= :symbol (first selection)) [symbol-section (second selection)])
|
(when palette-symbol? [palette-symbol-section (second selection)])
|
||||||
(when (= :symbol (first selection))
|
(when (and (= :symbol (first selection)) (not palette-symbol?))
|
||||||
(let [sid (second selection)
|
[symbol-section (second selection)])
|
||||||
frame (min (max 0 @(rf/subscribe [::render/open-frame]))
|
(when (and tracked? (not palette-placement?) (not palette-symbol?))
|
||||||
(max 0 (dec (or (clip-domain/frames clip sid) 1))))]
|
[tracking-section])]]))
|
||||||
[symbol-parameters-section sid frame "symbol parameters"]))
|
|
||||||
(when tracked? [tracking-section])]]))
|
|
||||||
|
|
|
||||||
|
|
@ -172,15 +172,16 @@
|
||||||
;; THE STAGE IS THE CLIP'S, not a constant. Project dimensions are
|
;; THE STAGE IS THE CLIP'S, not a constant. Project dimensions are
|
||||||
;; independent of the footage, so the size the frame is rasterised at comes
|
;; independent of the footage, so the size the frame is rasterised at comes
|
||||||
;; out of the document like everything else.
|
;; out of the document like everything else.
|
||||||
(let [ras (raster-for width height)]
|
(let [ras (raster-for width height)
|
||||||
(let [ops (resolver f)
|
ops (resolver f)
|
||||||
bg (pal/background-index palette (clip/active-palette resolver))]
|
active (clip/active-palette resolver)
|
||||||
(swap! state assoc :ops ops)
|
bg (pal/background-index palette active)]
|
||||||
(-> ras
|
(swap! state assoc :ops ops)
|
||||||
(raster/clear! bg)
|
(-> ras
|
||||||
(raster/draw-ops! ops)))
|
(raster/clear! bg)
|
||||||
|
(raster/draw-ops! ops))
|
||||||
(js/performance.mark "arthur/blit:start")
|
(js/performance.mark "arthur/blit:start")
|
||||||
(canvas/blit! canvas ras ramp)
|
(canvas/blit! canvas ras (pal/effective-ramp palette active))
|
||||||
(underlay/paint! (assoc underlay :width width :playing? playing?)
|
(underlay/paint! (assoc underlay :width width :playing? playing?)
|
||||||
resolver repaint!))
|
resolver repaint!))
|
||||||
(js/performance.measure "arthur/resolve+draw" "arthur/paint:start" "arthur/blit:start")
|
(js/performance.measure "arthur/resolve+draw" "arthur/paint:start" "arthur/blit:start")
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,22 @@
|
||||||
:on-double-click #(rf/dispatch [::project/default-palette id])}
|
:on-double-click #(rf/dispatch [::project/default-palette id])}
|
||||||
(carrying (str "palette:" id) #(drag/palette! id (:name p))))])
|
(carrying (str "palette:" id) #(drag/palette! id (:name p))))])
|
||||||
|
|
||||||
|
(defn- palette-transition-row [document sid selection rename]
|
||||||
|
(let [sym (clip/symbol document sid)
|
||||||
|
p (get (pal/palettes document) (:palette-ref sym))]
|
||||||
|
^{:key (str sid)}
|
||||||
|
[row {:label (clip/symbol-name document sid)
|
||||||
|
:sub (str "uses " (or (:name p) (:palette-ref sym)))
|
||||||
|
:title "Palette transition definition"
|
||||||
|
:on? (= selection [:symbol sid])
|
||||||
|
:rename (assoc rename
|
||||||
|
:key [:symbol sid]
|
||||||
|
:value (clip/symbol-name document sid)
|
||||||
|
:commit! (fn [value]
|
||||||
|
((:begin! rename) nil)
|
||||||
|
(rf/dispatch [::project/rename-symbol sid value])))
|
||||||
|
:on-click #(rf/dispatch [::ui/select [:symbol sid]])}]))
|
||||||
|
|
||||||
(defn- footage-row [{:keys [id label frames fps video] :as f} chosen rename]
|
(defn- footage-row [{:keys [id label frames fps video] :as f} chosen rename]
|
||||||
^{:key id}
|
^{:key id}
|
||||||
[row (merge {:label label
|
[row (merge {:label label
|
||||||
|
|
@ -403,23 +419,32 @@
|
||||||
|
|
||||||
Still not `:main` being special. The document says which symbol that is by its
|
Still not `:main` being special. The document says which symbol that is by its
|
||||||
structure; rename it, place it inside something else, and the pool follows."
|
structure; rename it, place it inside something else, and the pool follows."
|
||||||
[{:keys [document query searching? media sounds chosen rename main palette-choice] :as ctx}]
|
[{:keys [document query searching? media sounds chosen rename main palette-choice selection] :as ctx}]
|
||||||
(let [named? #(hit? query (clip/symbol-name document %))
|
(let [named? #(hit? query (clip/symbol-name document %))
|
||||||
top (when (and main (named? main)) main)
|
top (when (and main (named? main)) main)
|
||||||
rest (filterv #(and (named? %) (not= main %))
|
symbol-ids (sort-by str (keys (:symbols document)))
|
||||||
(sort-by str (keys (:symbols document))))
|
transition? #(= :palette (:type (clip/symbol document %)))
|
||||||
|
transitions (filterv #(and (named? %) (transition? %)) symbol-ids)
|
||||||
|
rest (filterv #(and (named? %) (not= main %)
|
||||||
|
(not (#{:palette :palette-track}
|
||||||
|
(:type (clip/symbol document %)))))
|
||||||
|
symbol-ids)
|
||||||
media (filterv #(hit? query (:label %)) media)
|
media (filterv #(hit? query (:label %)) media)
|
||||||
sounds (filterv #(hit? query (:label %)) sounds)
|
sounds (filterv #(hit? query (:label %)) sounds)
|
||||||
palettes (filterv #(hit? query (:name (val %)))
|
palettes (filterv #(hit? query (:name (val %)))
|
||||||
(sort-by (comp str :name val) (pal/palettes document)))]
|
(sort-by (comp str :name val) (pal/palettes document)))]
|
||||||
[sections searching?
|
[sections searching?
|
||||||
(+ (if top 1 0) (count rest) (count media) (count sounds) (count palettes))
|
(+ (if top 1 0) (count rest) (count transitions)
|
||||||
|
(count media) (count sounds) (count palettes))
|
||||||
[{:title "project" :searching? searching?
|
[{:title "project" :searching? searching?
|
||||||
:blank "nothing to open yet"
|
:blank "nothing to open yet"
|
||||||
:rows (when top [(symbol-row document top ctx)])}
|
:rows (when top [(symbol-row document top ctx)])}
|
||||||
{:title "symbols" :searching? searching?
|
{:title "symbols" :searching? searching?
|
||||||
:blank "nothing else in the library"
|
:blank "nothing else in the library"
|
||||||
:rows (mapv #(symbol-row document % ctx) rest)}
|
:rows (mapv #(symbol-row document % ctx) rest)}
|
||||||
|
{:title "palette transitions" :searching? searching?
|
||||||
|
:blank "no palette transitions"
|
||||||
|
:rows (mapv #(palette-transition-row document % selection rename) transitions)}
|
||||||
{:title "palettes" :searching? searching?
|
{:title "palettes" :searching? searching?
|
||||||
:blank "no palettes"
|
:blank "no palettes"
|
||||||
:rows (mapv (fn [[id p]] (palette-row id p (:default-palette document)
|
:rows (mapv (fn [[id p]] (palette-row id p (:default-palette document)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
THE CANVAS IS THE RASTER'S OWN SIZE, scaled by CSS. See `ui/canvas` for why
|
THE CANVAS IS THE RASTER'S OWN SIZE, scaled by CSS. See `ui/canvas` for why
|
||||||
that is load-bearing rather than convenient."
|
that is load-bearing rather than convenient."
|
||||||
(:require [arthur.domain.channel :as channel]
|
(:require [arthur.domain.channel :as channel]
|
||||||
|
[arthur.domain.clip :as clip]
|
||||||
[arthur.domain.gesture :as gesture]
|
[arthur.domain.gesture :as gesture]
|
||||||
[arthur.domain.nest :as nest]
|
[arthur.domain.nest :as nest]
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
|
|
@ -473,6 +474,15 @@
|
||||||
;; A drop lands at the playhead IN THE OPEN SYMBOL's frames, the same
|
;; A drop lands at the playhead IN THE OPEN SYMBOL's frames, the same
|
||||||
;; unit a drop on the timeline's ruler lands in.
|
;; unit a drop on the timeline's ruler lands in.
|
||||||
frame @(rf/subscribe [::render/open-frame])
|
frame @(rf/subscribe [::render/open-frame])
|
||||||
|
clip @(rf/subscribe [::render/clip])
|
||||||
|
clip-id @(rf/subscribe [::render/clip-id])
|
||||||
|
db-target @(rf/subscribe [::sub/target])
|
||||||
|
open @(rf/subscribe [::render/open])
|
||||||
|
destination (when clip
|
||||||
|
(ui/aimed-symbol clip (:store (store/entry clip-id))
|
||||||
|
{:ui {:open open :target db-target}} frame))
|
||||||
|
destination-name (when-let [sid (:sid destination)]
|
||||||
|
(clip/symbol-name clip sid))
|
||||||
zoom @(rf/subscribe [::layout/zoom :stage])]
|
zoom @(rf/subscribe [::layout/zoom :stage])]
|
||||||
[:div.stage-area
|
[:div.stage-area
|
||||||
[:div.stage-wrap
|
[:div.stage-wrap
|
||||||
|
|
@ -498,4 +508,6 @@
|
||||||
:height (str (* zoom h) "px")}}]
|
:height (str (* zoom h) "px")}}]
|
||||||
[:canvas.underlay {:ref #(underlay/set-canvas! %)
|
[:canvas.underlay {:ref #(underlay/set-canvas! %)
|
||||||
:width (* zoom w) :height (* zoom h)}]
|
:width (* zoom w) :height (* zoom h)}]
|
||||||
[overlay w h zoom]]]))
|
[overlay w h zoom]]
|
||||||
|
(when destination-name
|
||||||
|
[:div.stage-target "creating in " [:strong destination-name]])]))
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,7 @@
|
||||||
(-> (first (rows clip track-id expanded chosen))
|
(-> (first (rows clip track-id expanded chosen))
|
||||||
(assoc :path [::palette-track] :owner track-id
|
(assoc :path [::palette-track] :owner track-id
|
||||||
:label "palette" :kind :palette
|
:label "palette" :kind :palette
|
||||||
|
:expandable? false :expanded? false
|
||||||
:select [::palette-track sid]))
|
:select [::palette-track sid]))
|
||||||
{:path [::palette-track] :depth 0 :label "palette" :kind :palette
|
{:path [::palette-track] :depth 0 :label "palette" :kind :palette
|
||||||
:owner nil :select [::palette-track sid]
|
:owner nil :select [::palette-track sid]
|
||||||
|
|
@ -730,7 +731,11 @@
|
||||||
active-row (:row @sliding)
|
active-row (:row @sliding)
|
||||||
slide (fn [^js e]
|
slide (fn [^js e]
|
||||||
(let [{from :row pressed :from} @sliding]
|
(let [{from :row pressed :from} @sliding]
|
||||||
(when (= path from)
|
;; Pointer capture already makes this the one track receiving
|
||||||
|
;; the gesture. Do not compare its presentation path: the
|
||||||
|
;; palette lane is a view of another symbol and its synthetic
|
||||||
|
;; row address is not the authored clip path.
|
||||||
|
(when from
|
||||||
;; HOW MANY FRAMES THE POINTER HAS MOVED, as the difference
|
;; HOW MANY FRAMES THE POINTER HAS MOVED, as the difference
|
||||||
;; of two frames under it -- not a pixel distance scaled and
|
;; of two frames under it -- not a pixel distance scaled and
|
||||||
;; rounded by itself. The ghost below and the command on
|
;; rounded by itself. The ghost below and the command on
|
||||||
|
|
@ -779,7 +784,14 @@
|
||||||
target-sid (when target
|
target-sid (when target
|
||||||
(:sid (ui/drop-destination-at
|
(:sid (ui/drop-destination-at
|
||||||
clip store open target-at target)))
|
clip store open target-at target)))
|
||||||
|
;; Row identity answers before document addressing:
|
||||||
|
;; remaining on the DOM track that captured the
|
||||||
|
;; pointer is a slide. This is true for every lane
|
||||||
|
;; and avoids asking special rows (such as the
|
||||||
|
;; palette track) to counterfeit an ordinary target
|
||||||
|
;; address merely to recognize themselves.
|
||||||
landing? (and target-sid
|
landing? (and target-sid
|
||||||
|
(not (identical? target-el (.-currentTarget e)))
|
||||||
(not= target-sid (nth (:selection drag) 1)))
|
(not= target-sid (nth (:selection drag) 1)))
|
||||||
target-frame (when landing?
|
target-frame (when landing?
|
||||||
(max 0 (- target-at (:grab drag))))]
|
(max 0 (- target-at (:grab drag))))]
|
||||||
|
|
@ -814,7 +826,7 @@
|
||||||
(:kind @sliding) (:ripple? @sliding)
|
(:kind @sliding) (:ripple? @sliding)
|
||||||
(:other @sliding) owner]))))))))
|
(:other @sliding) owner]))))))))
|
||||||
done (fn [commit?]
|
done (fn [commit?]
|
||||||
(when (= path (:row @sliding))
|
(when @sliding
|
||||||
(let [{:keys [path df kind ripple? other target-lane target-frame
|
(let [{:keys [path df kind ripple? other target-lane target-frame
|
||||||
drag nest on-click more?]} @sliding]
|
drag nest on-click more?]} @sliding]
|
||||||
(reset! sliding nil)
|
(reset! sliding nil)
|
||||||
|
|
@ -880,11 +892,18 @@
|
||||||
;; one, else this row's own instance. It opens as a tab, which is what
|
;; one, else this row's own instance. It opens as a tab, which is what
|
||||||
;; double-clicking the same symbol in the pool does.
|
;; double-clicking the same symbol in the pool does.
|
||||||
:on-double-click (fn [^js e]
|
:on-double-click (fn [^js e]
|
||||||
(when-let [source (or (:source (clip-under e)) of)]
|
(when-let [source (and (not= :palette kind)
|
||||||
|
(or (:source (clip-under e)) of))]
|
||||||
(.stopPropagation e)
|
(.stopPropagation e)
|
||||||
(rf/dispatch [::pb/open-symbol source])))
|
(rf/dispatch [::pb/open-symbol source])))
|
||||||
:ref (when lane? (fn [el] (when el (aset el "arthurLane" select))))
|
:ref (when lane? (fn [el] (when el (aset el "arthurLane" select))))
|
||||||
:on-drag-enter (fn [^js e]
|
:on-drag-enter (fn [^js e]
|
||||||
|
;; A palette hover belongs only to the palette row. If
|
||||||
|
;; the pointer leaves it for an ordinary row, clear its
|
||||||
|
;; last accepted preview even though this row correctly
|
||||||
|
;; declines the native drop.
|
||||||
|
(when (and (drag/palette?) (not= :palette kind))
|
||||||
|
(rf/dispatch [::ui/drop-clear]))
|
||||||
(cond
|
(cond
|
||||||
(and (= :palette kind) (drag/palette?))
|
(and (= :palette kind) (drag/palette?))
|
||||||
(do (.preventDefault e) (.stopPropagation e))
|
(do (.preventDefault e) (.stopPropagation e))
|
||||||
|
|
@ -947,6 +966,9 @@
|
||||||
:on-pointer-down
|
:on-pointer-down
|
||||||
(when (and select (not unmapped?))
|
(when (and select (not unmapped?))
|
||||||
#(begin! % (or slides path) :slide select nil nil))}
|
#(begin! % (or slides path) :slide select nil nil))}
|
||||||
|
(when (and select (not unmapped?))
|
||||||
|
[:span.tl-edge.in {:title "Drag start"
|
||||||
|
:on-pointer-down #(begin! % path :in select nil nil)}])
|
||||||
(when (and select (not unmapped?))
|
(when (and select (not unmapped?))
|
||||||
[:span.tl-edge.out {:title "Drag endpoint · Shift-drag ripples later clips"
|
[:span.tl-edge.out {:title "Drag endpoint · Shift-drag ripples later clips"
|
||||||
:on-pointer-down #(begin! % path :out select nil nil)}])]))
|
:on-pointer-down #(begin! % path :out select nil nil)}])]))
|
||||||
|
|
@ -966,8 +988,10 @@
|
||||||
:when (< in out)]
|
:when (< in out)]
|
||||||
^{:key (str id)}
|
^{:key (str id)}
|
||||||
[:button.tl-cel
|
[:button.tl-cel
|
||||||
{:title (str label " · select clip; double-click to edit its symbol"
|
{:title (if (= :palette kind)
|
||||||
" · shift-drag another clip onto it to nest that clip inside")
|
(str label " · select palette transition")
|
||||||
|
(str label " · select clip; double-click to edit its symbol"
|
||||||
|
" · shift-drag another clip onto it to nest that clip inside"))
|
||||||
;; WHICH CLIP IS SELECTED IS THE QUESTION THIS ROW ANSWERS. An
|
;; WHICH CLIP IS SELECTED IS THE QUESTION THIS ROW ANSWERS. An
|
||||||
;; expanded lane opens exactly the selected clip, so a block that
|
;; expanded lane opens exactly the selected clip, so a block that
|
||||||
;; cannot say whether it is the one leaves the portal below it
|
;; cannot say whether it is the one leaves the portal below it
|
||||||
|
|
@ -1114,19 +1138,19 @@
|
||||||
{:id ::drop :label (str "+ " (:label drop)) :ghost? true
|
{:id ::drop :label (str "+ " (:label drop)) :ghost? true
|
||||||
:span drop-span})
|
:span drop-span})
|
||||||
chosen (when (= :node (first selection)) (nth selection 3))
|
chosen (when (= :node (first selection)) (nth selection 3))
|
||||||
picture (cond->> (cond->> (into [(palette-track-row clip open frames expanded chosen)]
|
picture (cond-> (cond->> (into [(palette-track-row clip open frames expanded chosen)]
|
||||||
(rows clip open expanded chosen))
|
(rows clip open expanded chosen))
|
||||||
lane-ghost
|
lane-ghost
|
||||||
(mapv (fn [row]
|
(mapv (fn [row]
|
||||||
(if (= drop-lane (:select row))
|
(if (= drop-lane (:select row))
|
||||||
(update row :cels (fnil conj []) lane-ghost)
|
(update row :cels (fnil conj []) lane-ghost)
|
||||||
row))))
|
row))))
|
||||||
;; An untargeted visual drop becomes a new ordinary row
|
;; An untargeted visual drop becomes a new ordinary row
|
||||||
;; at the bottom. Inserting it above the lanes shifts the
|
;; at the bottom. Inserting it above the lanes shifts the
|
||||||
;; lane under the pointer and makes the browser alternate
|
;; lane under the pointer and makes the browser alternate
|
||||||
;; between this ghost and that lane's drop target.
|
;; between this ghost and that lane's drop target.
|
||||||
(and ghost (not (:sound? drop)))
|
(and ghost (not (:sound? drop)))
|
||||||
(fn [rows] (conj (vec rows) ghost)))
|
(conj ghost))
|
||||||
sounds (cond->> (into (vec (filter :sound? picture))
|
sounds (cond->> (into (vec (filter :sound? picture))
|
||||||
(sound-rows clip open expanded))
|
(sound-rows clip open expanded))
|
||||||
(and ghost (:sound? drop)) (cons ghost))
|
(and ghost (:sound? drop)) (cons ghost))
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,14 @@
|
||||||
(is (seq (ch/problems (ch/keyed {0 :a, 10 :b} :linear)))
|
(is (seq (ch/problems (ch/keyed {0 :a, 10 :b} :linear)))
|
||||||
"a cut or tone cannot interpolate"))
|
"a cut or tone cannot interpolate"))
|
||||||
|
|
||||||
|
(deftest palette-choice-channels-can-cut-or-blend
|
||||||
|
(let [held (assoc (ch/keyed {0 :day 10 :night} :hold) :semantic :palette)
|
||||||
|
blend (assoc held :interp :linear)]
|
||||||
|
(is (empty? (ch/problems blend)))
|
||||||
|
(is (= :day (ch/value-at held 5 nil)))
|
||||||
|
(is (= {:from :day :to :night :t 0.5}
|
||||||
|
(ch/value-at blend 5 nil)))))
|
||||||
|
|
||||||
(deftest numeric-channels-can-ramp-between-keys
|
(deftest numeric-channels-can-ramp-between-keys
|
||||||
(let [c (ch/keyed {0 0.0, 10 1.0} :linear)
|
(let [c (ch/keyed {0 0.0, 10 1.0} :linear)
|
||||||
cursor (ch/cursor c nil)]
|
cursor (ch/cursor c nil)]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
[arthur.domain.channel :as ch]
|
[arthur.domain.channel :as ch]
|
||||||
[arthur.domain.clip :as clip]
|
[arthur.domain.clip :as clip]
|
||||||
[arthur.domain.leaf :as leaf]
|
[arthur.domain.leaf :as leaf]
|
||||||
|
[arthur.domain.nest :as nest]
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.paint :as paint]
|
[arthur.domain.paint :as paint]
|
||||||
[arthur.domain.pose :as pose]
|
[arthur.domain.pose :as pose]
|
||||||
|
|
@ -362,3 +363,46 @@
|
||||||
(is (= :night (clip/active-palette main)))
|
(is (= :night (clip/active-palette main)))
|
||||||
(is (= (pal/render-index context :night 0)
|
(is (= (pal/render-index context :night 0)
|
||||||
(pal/background-index context (clip/active-palette main))))))
|
(pal/background-index context (clip/active-palette main))))))
|
||||||
|
|
||||||
|
(deftest a-palette-lane-can-blend-between-keyed-palettes
|
||||||
|
(let [palette (fn [id color]
|
||||||
|
{:id id :name (name id) :slots [{:hex color} {:hex color}]})
|
||||||
|
mark {:id :mark :kind :rect :z "a1"
|
||||||
|
:channels {[:geom :size] (ch/framed 2)
|
||||||
|
[:style :color] (ch/framed 1)}}
|
||||||
|
choice (assoc (ch/keyed {0 :day 4 :night} :linear) :semantic :palette)
|
||||||
|
document {:fps 30 :width 20 :height 20
|
||||||
|
:palettes {:day (palette :day "#000000")
|
||||||
|
:night (palette :night "#ffffff")}
|
||||||
|
:default-palette :day
|
||||||
|
:symbols
|
||||||
|
{:main {:id :main :frames 5 :palette-track :palettes
|
||||||
|
:nodes {:drawing {:id :drawing :kind :instance :z "a1"
|
||||||
|
:source {:symbol :drawing}}}}
|
||||||
|
:palettes {:id :palettes :type :palette-track :display :lane :frames 5
|
||||||
|
:nodes {:change {:id :change :kind :instance :z "a1"
|
||||||
|
:source {:symbol :day-palette}
|
||||||
|
:span [0 5] :time {:at 0}
|
||||||
|
:channels {[:palette] choice}}}}
|
||||||
|
:day-palette {:id :day-palette :type :palette :palette-ref :day
|
||||||
|
:frames 1 :nodes {}}
|
||||||
|
:drawing {:id :drawing :frames 5 :nodes {:mark mark}}}}
|
||||||
|
context (pal/compile document)
|
||||||
|
resolve (clip/resolver document :main nil context nil)
|
||||||
|
slid (nest/slide document :palettes [:change] 1)
|
||||||
|
trimmed (nest/resize-in document :palettes [:change] 1)
|
||||||
|
extended (nest/resize-out document :palettes [:change] 1 false)]
|
||||||
|
(resolve 2)
|
||||||
|
(is (= {:from :day :to :night :t 0.5} (clip/active-palette resolve)))
|
||||||
|
(is (= [128 128 128]
|
||||||
|
(nth (pal/effective-ramp context (clip/active-palette resolve))
|
||||||
|
(pal/render-index context :day 1))))
|
||||||
|
(is (nil? (:refused slid)) (:refused slid))
|
||||||
|
(is (= [1 6] (node/placed-span
|
||||||
|
(get-in slid [:clip :symbols :palettes :nodes :change]))))
|
||||||
|
(is (nil? (:refused trimmed)) (:refused trimmed))
|
||||||
|
(is (= [1 5] (node/placed-span
|
||||||
|
(get-in trimmed [:clip :symbols :palettes :nodes :change]))))
|
||||||
|
(is (nil? (:refused extended)) (:refused extended))
|
||||||
|
(is (= [0 6] (node/placed-span
|
||||||
|
(get-in extended [:clip :symbols :palettes :nodes :change]))))))
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,13 @@
|
||||||
(is (nil? (get-in after [:ui :selection])))
|
(is (nil? (get-in after [:ui :selection])))
|
||||||
(is (nil? (get-in after [:ui :target])))))
|
(is (nil? (get-in after [:ui :target])))))
|
||||||
|
|
||||||
|
(deftest clearing-selection-also-clears-the-creation-target
|
||||||
|
(let [db {:ui {:selection [:symbol :drawing]
|
||||||
|
:target {:sid :main :id :lane :path [:lane]}}}
|
||||||
|
after (ui/selected db nil)]
|
||||||
|
(is (nil? (get-in after [:ui :selection])))
|
||||||
|
(is (nil? (get-in after [:ui :target])))))
|
||||||
|
|
||||||
(deftest an-explicit-lane-is-one-row-of-clips
|
(deftest an-explicit-lane-is-one-row-of-clips
|
||||||
(let [doc (fixture/document)
|
(let [doc (fixture/document)
|
||||||
rows (timeline/rows doc :main #{})
|
rows (timeline/rows doc :main #{})
|
||||||
|
|
@ -206,6 +213,14 @@
|
||||||
(is (= address (get-in after [:ui :selection])))
|
(is (= address (get-in after [:ui :selection])))
|
||||||
(is (= [address] (get-in after [:ui :selections])))))
|
(is (= [address] (get-in after [:ui :selections])))))
|
||||||
|
|
||||||
|
(deftest selecting-or-trimming-a-nested-lane-item-does-not-open-its-row
|
||||||
|
(let [selection [:node :inner :clip [:lane :clip]]
|
||||||
|
before {:ui {:expanded #{[:already-open]}}}
|
||||||
|
after (ui/selected before selection)]
|
||||||
|
(is (= selection (get-in after [:ui :selection])))
|
||||||
|
(is (= #{[:already-open]} (get-in after [:ui :expanded]))
|
||||||
|
"disclosure is changed only by the twist control")))
|
||||||
|
|
||||||
(deftest finishing-a-polygon-opens-no-rows
|
(deftest finishing-a-polygon-opens-no-rows
|
||||||
;; Expansion is the twist triangle's business. Finishing a shape used to open
|
;; Expansion is the twist triangle's business. Finishing a shape used to open
|
||||||
;; every row down to it, which inside a lane meant tearing its one row into a
|
;; every row down to it, which inside a lane meant tearing its one row into a
|
||||||
|
|
|
||||||
|
|
@ -841,16 +841,31 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
||||||
.stage-area {
|
.stage-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: grid;
|
display: flex;
|
||||||
/* `safe`, because the stage zooms: a centred grid item larger than its scroll
|
flex-direction: column;
|
||||||
|
/* `safe`, because the stage zooms: a centred item larger than its scroll
|
||||||
container puts its top-left edge out of reach on the start side, which at
|
container puts its top-left edge out of reach on the start side, which at
|
||||||
8x is most of the picture. Safe alignment falls back to start when it does
|
8x is most of the picture. Safe alignment falls back to start when it does
|
||||||
not fit, and centring is only ever about where a SMALL stage sits. */
|
not fit, and centring is only ever about where a SMALL stage sits. */
|
||||||
place-items: safe center;
|
align-items: safe center;
|
||||||
|
justify-content: safe center;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stage-target {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-top: -1px;
|
||||||
|
padding: 3px 9px 4px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-top: 0;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
background: var(--pane);
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-target strong { color: var(--fg); font-weight: 600; }
|
||||||
|
|
||||||
/* The stage sits ON the work area rather than filling it, as it does in every
|
/* The stage sits ON the work area rather than filling it, as it does in every
|
||||||
tool of this shape: the picture has edges and they are part of what you are
|
tool of this shape: the picture has edges and they are part of what you are
|
||||||
judging. */
|
judging. */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue