Scope inspector controls and key shape colors
This commit is contained in:
parent
a834ccb1e2
commit
2697401aad
5 changed files with 159 additions and 42 deletions
|
|
@ -99,15 +99,30 @@
|
|||
(or (:dense c) (:generated c)))
|
||||
[:pos :rot :scale])))
|
||||
|
||||
(defn hold-only?
|
||||
"Channels whose values are choices, not quantities. Their keys may change at
|
||||
a frame boundary but there is no meaningful value between two keys."
|
||||
[path value]
|
||||
(or (= path [:style :color])
|
||||
(boolean? value)
|
||||
(and (keyword? value) (not= path [:palette]))))
|
||||
|
||||
(defn- enforce-hold [path c]
|
||||
(if (and (= path [:style :color]) (:keys c))
|
||||
(-> c (assoc :interp :hold) (dissoc :segments))
|
||||
c))
|
||||
|
||||
(defn set-channel
|
||||
"Write `v` into channel `path`: a key on the node's own frame `f` when the
|
||||
channel is keyed, its one value when it is not."
|
||||
[n path f v]
|
||||
(let [c (get (channels n) path)]
|
||||
(assoc-in n [:channels path]
|
||||
(enforce-hold
|
||||
path
|
||||
(if (:keys c)
|
||||
(assoc-in c [:keys f] v)
|
||||
(merge (select-keys c [:semantic]) (ch/framed v))))))
|
||||
(merge (select-keys c [:semantic]) (ch/framed v)))))))
|
||||
|
||||
(defn set-keyed-channel
|
||||
"Write `v` as a key at `f`, starting an animated channel when needed. This is
|
||||
|
|
@ -116,10 +131,12 @@
|
|||
[n path f v]
|
||||
(let [c (get (channels n) path)]
|
||||
(assoc-in n [:channels path]
|
||||
(enforce-hold
|
||||
path
|
||||
(if (:keys c)
|
||||
(assoc-in c [:keys f] v)
|
||||
(merge (select-keys c [:semantic])
|
||||
(ch/keyed {f v} (if (or (boolean? v) (keyword? v)) :hold :linear)))))))
|
||||
(ch/keyed {f v} (if (hold-only? path v) :hold :linear))))))))
|
||||
|
||||
(defn toggle-key
|
||||
"Key channel `path` on the node's own frame `f` with the value it has there, or
|
||||
|
|
@ -127,19 +144,22 @@
|
|||
the last one off leaves it that one value. A boolean holds; anything else tweens.
|
||||
|
||||
`store` because the value it keys is read out of the channel, and a measured
|
||||
channel's values live in tier 2."
|
||||
channel's values live in tier 2. Colour is always held even though current
|
||||
documents store palette choices as numeric slot indices."
|
||||
[n path f store]
|
||||
(let [c (get (channels n) path)
|
||||
v (ch/value-at c f store)
|
||||
ks (dissoc (:keys c) f)]
|
||||
(assoc-in n [:channels path]
|
||||
(enforce-hold
|
||||
path
|
||||
(cond
|
||||
(not (:keys c)) (merge (select-keys c [:semantic])
|
||||
(ch/keyed {f v} (if (or (boolean? v) (keyword? v)) :hold :linear)))
|
||||
(ch/keyed {f v} (if (hold-only? path v) :hold :linear)))
|
||||
(not (contains? (:keys c) f)) (assoc-in c [:keys f] v)
|
||||
(seq ks) (cond-> (assoc c :keys ks)
|
||||
(:segments c) (update :segments dissoc f))
|
||||
:else (ch/framed v)))))
|
||||
:else (ch/framed v))))))
|
||||
|
||||
(defn set-segment-interp
|
||||
"Choose how channel `path`'s key at `left` leads to the next one: `:hold` cuts
|
||||
|
|
@ -147,7 +167,9 @@
|
|||
only a gap that exists, between a key and a later one, can be chosen."
|
||||
[n path left interp]
|
||||
(let [ks (:keys (get (channels n) path))]
|
||||
(if (and (contains? ks left) (some #(< left %) (keys ks)) (#{:hold :linear} interp))
|
||||
(if (and (contains? ks left) (some #(< left %) (keys ks))
|
||||
(#{:hold :linear} interp)
|
||||
(or (= :hold interp) (not (hold-only? path nil))))
|
||||
(assoc-in n [:channels path :segments left] interp)
|
||||
n)))
|
||||
|
||||
|
|
|
|||
|
|
@ -395,7 +395,10 @@
|
|||
|
||||
(rf/reg-event-db
|
||||
::choose
|
||||
(fn [db [_ id]] (assoc-in db [:footage :chosen] id)))
|
||||
(fn [db [_ id]]
|
||||
(-> db
|
||||
(assoc-in [:footage :chosen] id)
|
||||
(ui/selected [:footage id]))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; renaming an asset
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
"The right pane: what the selection is, and what can be changed about it.
|
||||
|
||||
Sections rather than a mode switch. The clip's facts are always true, so the
|
||||
clip section is always there; the node and symbol sections appear when
|
||||
something of that kind is selected; the tracking section appears when the clip
|
||||
has analysis in it. Nothing here computes — every control dispatches an intent
|
||||
and every readout comes off a subscription."
|
||||
clip section is always there; node, footage, face and tracking sections follow
|
||||
the thing actually selected. Nothing here computes — every control dispatches
|
||||
an intent and every readout comes off a subscription."
|
||||
(:require [clojure.string :as str]
|
||||
[arthur.domain.clip :as clip-domain]
|
||||
[arthur.domain.channel :as channel]
|
||||
|
|
@ -208,12 +207,42 @@
|
|||
v)))
|
||||
(when gap? [segment-select sid id path ch left])]))
|
||||
|
||||
(defn- color-control [sid id ch frame auto-key? palette]
|
||||
(let [keyed? (some? (:keys ch))
|
||||
value (channel/value-at ch (or frame 0) nil)
|
||||
value (if (integer? value)
|
||||
value
|
||||
(or (first (keep-indexed #(when (= value (:name %2)) %1)
|
||||
(:slots palette))) 0))
|
||||
off? (and keyed? (nil? frame))]
|
||||
[:dd.channel {:class (when auto-key? "live")}
|
||||
[:button.key {:class (cond (contains? (:keys ch) frame) "on" keyed? "keyed")
|
||||
:disabled (nil? frame)
|
||||
:title (if keyed? (str (count (:keys ch)) " held keys") "key color here")
|
||||
:on-click #(rf/dispatch [::project/toggle-key sid id [:style :color] frame])}
|
||||
"◆"]
|
||||
[:div.channel-swatches
|
||||
(doall
|
||||
(for [[i {:keys [name hex]}] (map-indexed vector (:slots palette))]
|
||||
^{:key i}
|
||||
[:button.swatch
|
||||
{:class (when (= i value) "on")
|
||||
:style {:background hex}
|
||||
:disabled off?
|
||||
:aria-label (str "color " i (when name (str " " (clojure.core/name name))))
|
||||
:aria-pressed (= i value)
|
||||
:title (str i (when name (str " · " (clojure.core/name name))) " · " hex)
|
||||
:on-click #(rf/dispatch [::project/set-channel sid id [:style :color] frame i])}]))]
|
||||
(when keyed? [:span.dim "hold"])]))
|
||||
|
||||
(defn- node-section [[sid id n]]
|
||||
(let [[start end] (:span n)
|
||||
auto-key? @(rf/subscribe [::sub/auto-key?])
|
||||
clip @(rf/subscribe [::render/clip])
|
||||
local @(rf/subscribe [::sub/selected-local])
|
||||
frame (:frame local)]
|
||||
frame (:frame local)
|
||||
palette-id (or (get-in clip [:symbols sid :palette]) (pal/default-palette-id clip))
|
||||
palette (get (pal/palettes clip) palette-id)]
|
||||
[section (str (name (:kind n)) " · in " (name sid))
|
||||
[facts
|
||||
"id" (brief id)
|
||||
|
|
@ -232,13 +261,24 @@
|
|||
(let [{:keys [frame]} local]
|
||||
[:dl.facts
|
||||
(doall
|
||||
(for [[path ch] (sort-by (comp str key) (node/channels n))]
|
||||
(for [[path ch] (sort-by (fn [[path]]
|
||||
[(cond
|
||||
(= :xform (first path)) 0
|
||||
(= path [:style :color]) 1
|
||||
:else 2)
|
||||
(str path)])
|
||||
(node/channels n))]
|
||||
^{:key (str path)}
|
||||
[:<>
|
||||
[:dt (str/join " " (map name path))]
|
||||
(if (and (contains? (node/defaults-of n) path) (not (:dense ch)))
|
||||
(cond
|
||||
(and (= path [:style :color]) (not (:dense ch)))
|
||||
[color-control sid id ch frame auto-key? palette]
|
||||
|
||||
(and (contains? (node/defaults-of n) path) (not (:dense ch)))
|
||||
[channel-control sid id path ch frame auto-key?]
|
||||
[:dd (channel-state ch)])]))])]))
|
||||
|
||||
:else [:dd (channel-state ch)])]))])]))
|
||||
|
||||
(defn- palette-placement-section [[sid id n]]
|
||||
(let [clip @(rf/subscribe [::render/clip])
|
||||
|
|
@ -431,19 +471,16 @@
|
|||
;; the footage showing under the picture
|
||||
|
||||
(defn- footage-section
|
||||
"The footage under the faces the open symbol has, on or off and how strongly.
|
||||
`here` is those faces.
|
||||
"The footage under the explicitly selected face or footage asset, on or off
|
||||
and how strongly. `here` is exactly those faces.
|
||||
|
||||
ONE SWITCH FOR THE FACES THAT ARE HERE. A face's footage is the face's, not a
|
||||
placement's, so there is nothing to inherit and nothing to set twice; with
|
||||
several faces in a take the box says how many are showing and switches the rest
|
||||
on, and one face alone is switched from its own timeline row.
|
||||
|
||||
THE OPEN SYMBOL'S FACES AND NOT THE SELECTION'S, which is what lets this live in
|
||||
the inspector at all: a viewing aid that appeared only once the right row had
|
||||
been found would make the way to see the footage you are tracing depend on what
|
||||
you had clicked. So the section is there whenever the picture on the stage has
|
||||
any footage behind it, wherever the selection happens to be."
|
||||
This is editor state, but the inspector still obeys selection scope: selecting
|
||||
an unrelated shape must neither expose nor mutate a face's viewing aid."
|
||||
[here]
|
||||
(let [{:keys [faces opacity]} @(rf/subscribe [::render/tracing])
|
||||
on (filterv (set faces) here)]
|
||||
|
|
@ -657,13 +694,45 @@
|
|||
(max 1 (* 2 default)))
|
||||
:step (cond even? 2 (= type :integer) 1 :else 0.01)})
|
||||
|
||||
(defn- tracking-section []
|
||||
(defn- subject-of-owner [clip [scope id]]
|
||||
(case scope
|
||||
:subject id
|
||||
:feature (get-in clip [:features id :subject])
|
||||
:group (get-in clip [:groups id :subject])
|
||||
nil))
|
||||
|
||||
(defn- face-selection
|
||||
"The one face explicitly named by a face symbol, its placement, or a tracking
|
||||
owner. A shape merely living inside an open face is deliberately not one."
|
||||
[clip selection selected-node]
|
||||
(let [[kind id] selection
|
||||
placed (node/source (peek selected-node))
|
||||
candidate (cond
|
||||
(= :symbol kind) id
|
||||
(#{:subject :feature :group} kind) (subject-of-owner clip selection)
|
||||
(= :node kind) placed)]
|
||||
(when (and candidate (trace/traceable? clip candidate)) candidate)))
|
||||
|
||||
(defn- footage-faces [clip selection selected-node]
|
||||
(if (= :footage (first selection))
|
||||
(let [footage-id (second selection)]
|
||||
(into [] (comp (filter #(= footage-id (:footage (val %)))) (map key))
|
||||
(:subjects clip)))
|
||||
(some-> (face-selection clip selection selected-node) vector)))
|
||||
|
||||
(defn- tracking-owners [clip selection selected-node]
|
||||
(let [faces (if (= :footage (first selection))
|
||||
(set (footage-faces clip selection selected-node))
|
||||
(some-> (face-selection clip selection selected-node) hash-set))]
|
||||
(when (seq faces)
|
||||
(filterv #(contains? faces (subject-of-owner clip %)) (owners clip)))))
|
||||
|
||||
(defn- tracking-section [all]
|
||||
(let [clip @(rf/subscribe [::render/clip])
|
||||
selection @(rf/subscribe [::sub/selection])
|
||||
knobs @(rf/subscribe [::sub/knobs])
|
||||
busy? (:busy? @(rf/subscribe [::playback/project]))
|
||||
report @(rf/subscribe [::project/regeneration])
|
||||
all (owners clip)
|
||||
[scope id :as owner] (if (some #{selection} all) selection (first all))
|
||||
area (case scope
|
||||
:subject :subject
|
||||
|
|
@ -727,7 +796,6 @@
|
|||
open @(rf/subscribe [::render/open])
|
||||
selection @(rf/subscribe [::sub/selection])
|
||||
node @(rf/subscribe [::sub/selected-node])
|
||||
tracked? (seq (owners clip))
|
||||
;; The face the tracing section is about: the SELECTED PLACEMENT's symbol,
|
||||
;; or, when the selection is not an instance or there is none, the OPEN
|
||||
;; symbol — which is the face itself when a face is open to be drawn over.
|
||||
|
|
@ -737,6 +805,8 @@
|
|||
;; lane traces is not a question with one answer, and naming the drawing
|
||||
;; showing now would move the section under the playhead.
|
||||
placed (node/source (peek node))
|
||||
footage-faces (footage-faces clip selection node)
|
||||
tracking-owners (tracking-owners clip selection node)
|
||||
palette-placement? (and node
|
||||
(= :palette-track
|
||||
(get-in clip [:symbols (first node) :type])))
|
||||
|
|
@ -744,10 +814,6 @@
|
|||
(= :palette (get-in clip [:symbols (second selection) :type])))
|
||||
face (or placed (when (trace/traceable? clip open) open))
|
||||
faces (when face (trace/faces clip face))
|
||||
;; Every face the open symbol has, which is what the footage switch is
|
||||
;; about: the stage either has footage behind it or it has none, and that
|
||||
;; does not depend on what is selected.
|
||||
here (trace/traceable-faces clip open)
|
||||
;; Where that face sits, as a row path from the open symbol, so the faces
|
||||
;; inside it can be selected by their own rows. A selection made on the
|
||||
;; stage has no path and names a node directly in the open symbol; the
|
||||
|
|
@ -763,12 +829,13 @@
|
|||
(when (and placed (not palette-placement?)) [symbol-section placed "source symbol"])
|
||||
(when (and node (not palette-placement?)) ^{:key (str (first node) "/" (second node))}
|
||||
[correction-section node])
|
||||
(when (and (not palette-placement?) (seq here)) [footage-section here])
|
||||
(when (and (not palette-placement?) (seq footage-faces))
|
||||
[footage-section footage-faces])
|
||||
(when (and face (or (trace/traceable? clip face) (seq faces)))
|
||||
[tracing-section face faces path])
|
||||
(when face ^{:key (str "perf/" face)} [performance-section face])
|
||||
(when palette-symbol? [palette-symbol-section (second selection)])
|
||||
(when (and (= :symbol (first selection)) (not palette-symbol?))
|
||||
[symbol-section (second selection)])
|
||||
(when (and tracked? (not palette-placement?) (not palette-symbol?))
|
||||
[tracking-section])]]))
|
||||
(when (and (seq tracking-owners) (not palette-placement?) (not palette-symbol?))
|
||||
[tracking-section tracking-owners])]]))
|
||||
|
|
|
|||
|
|
@ -226,3 +226,20 @@
|
|||
(is (= :linear (get-in moved [:channels [:xform :rot] :interp])))
|
||||
(is (= :hold (get-in visible [:channels [:vis] :interp]))
|
||||
"boolean parameters do not tween")))
|
||||
|
||||
(deftest colour-keys-are-discrete
|
||||
(let [n {:id :x :kind :poly
|
||||
:channels {[:style :color] (ch/framed 2)}}
|
||||
keyed (node/set-keyed-channel n [:style :color] 3 4)
|
||||
two (node/set-keyed-channel keyed [:style :color] 8 7)]
|
||||
(is (= :hold (get-in two [:channels [:style :color] :interp]))
|
||||
"numeric palette slots still hold")
|
||||
(is (= two (node/set-segment-interp two [:style :color] 3 :linear))
|
||||
"a caller cannot introduce a colour tween")
|
||||
(is (= :hold (get-in (node/set-channel
|
||||
(assoc-in two [:channels [:style :color] :interp] :linear)
|
||||
[:style :color] 8 6)
|
||||
[:channels [:style :color] :interp]))
|
||||
"editing repairs an older tweened colour channel")
|
||||
(is (= :hold (get-in (node/toggle-key n [:style :color] 3 nil)
|
||||
[:channels [:style :color] :interp])))))
|
||||
|
|
|
|||
|
|
@ -1384,6 +1384,14 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
|||
.facts dd.channel .key { padding: 0 3px; color: var(--dim); }
|
||||
.facts dd.channel .key.keyed { color: var(--fg); }
|
||||
.facts dd.channel .key.on { color: var(--sel); }
|
||||
.facts dd.channel .channel-swatches {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
.facts dd.channel .channel-swatches .swatch { flex: 0 0 15px; }
|
||||
.facts dd.channel.live {
|
||||
margin: -2px;
|
||||
padding: 2px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue