Model palette tracks as typed symbol lanes
This commit is contained in:
parent
dff23d7994
commit
26fab9392e
20 changed files with 371 additions and 123 deletions
45
clips/migrations/0013_palette_track.py
Normal file
45
clips/migrations/0013_palette_track.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""Schema 4: split animated symbol palettes from the authoring palette.
|
||||||
|
|
||||||
|
Before schema 4 a symbol's ``:palette`` leaf value was always a channel. It now
|
||||||
|
names the static palette used when that symbol is the viewed root, while the
|
||||||
|
old channel is retained as ``:palette-channel`` compatibility data. New edits
|
||||||
|
use a real lane symbol referenced by ``:palette-track``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
Leaf = apps.get_model("clips", "Leaf")
|
||||||
|
Project = apps.get_model("clips", "Project")
|
||||||
|
for leaf in Leaf.objects.filter(path__contains="/symbol/"):
|
||||||
|
parts = leaf.path.split("/")
|
||||||
|
if len(parts) != 4 or parts[2] != "symbol":
|
||||||
|
continue
|
||||||
|
value = leaf.value
|
||||||
|
if not (isinstance(value, list) and value[:1] == ["^ "]):
|
||||||
|
continue
|
||||||
|
out = list(value)
|
||||||
|
changed = False
|
||||||
|
for i in range(1, len(out) - 1, 2):
|
||||||
|
if out[i] == "~:palette" and isinstance(out[i + 1], list):
|
||||||
|
out[i] = "~:palette-channel"
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
leaf.value = out
|
||||||
|
leaf.version += 1
|
||||||
|
leaf.save(update_fields=["value", "version"])
|
||||||
|
Project.objects.update(schema_version=4)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [("clips", "0012_sound_label")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="project",
|
||||||
|
name="schema_version",
|
||||||
|
field=models.PositiveIntegerField(default=4),
|
||||||
|
),
|
||||||
|
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
|
|
@ -235,7 +235,7 @@ class Project(models.Model):
|
||||||
settings.AUTH_USER_MODEL, blank=True, related_name="shared_projects",
|
settings.AUTH_USER_MODEL, blank=True, related_name="shared_projects",
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=200, default="untitled")
|
name = models.CharField(max_length=200, default="untitled")
|
||||||
schema_version = models.PositiveIntegerField(default=3)
|
schema_version = models.PositiveIntegerField(default=4)
|
||||||
seq = models.PositiveBigIntegerField(default=0)
|
seq = models.PositiveBigIntegerField(default=0)
|
||||||
palette = models.CharField(max_length=64, default="arthur/default")
|
palette = models.CharField(max_length=64, default="arthur/default")
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
||||||
|
|
@ -435,7 +435,7 @@ class DocumentTests(TestCase):
|
||||||
self.assertEqual(5, len(response.json()["written"]))
|
self.assertEqual(5, len(response.json()["written"]))
|
||||||
|
|
||||||
loaded = self.client.get(f"/api/projects/{self.project.id}").json()
|
loaded = self.client.get(f"/api/projects/{self.project.id}").json()
|
||||||
self.assertEqual(3, loaded["schema_version"])
|
self.assertEqual(4, loaded["schema_version"])
|
||||||
self.assertEqual(1, len(loaded["clips"]))
|
self.assertEqual(1, len(loaded["clips"]))
|
||||||
clip = loaded["clips"][0]
|
clip = loaded["clips"][0]
|
||||||
self.assertEqual("c1", clip["cid"])
|
self.assertEqual("c1", clip["cid"])
|
||||||
|
|
|
||||||
|
|
@ -718,28 +718,33 @@ scope does not define resolves to the loud magenta, like any other missing index
|
||||||
|
|
||||||
### The scope rule
|
### The scope rule
|
||||||
|
|
||||||
`:palette` on a timeline is a channel like any other:
|
`:palette-track` points to an ordinary lane symbol. Its clips are instances of
|
||||||
|
restricted palette symbols: a palette symbol owns no nodes and points at exactly
|
||||||
|
one project palette.
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
{:frames 91
|
{:id :shot :frames 91 :palette :day :palette-track :shot-palettes :nodes {...}}
|
||||||
:palette {:animated? true :interp :hold :keys {0 :day, 48 :dusk, 72 :night}}
|
|
||||||
:nodes {...}}
|
{:id :shot-palettes :type :palette-track :display :lane :frames 91
|
||||||
|
:nodes {:day-clip {:kind :instance :source {:symbol :day-palette} ...}
|
||||||
|
:dusk-clip {:kind :instance :source {:symbol :dusk-palette} ...}}}
|
||||||
|
|
||||||
|
{:id :day-palette :type :palette :palette-ref :day :frames 1 :nodes {}}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Absent means inherit** from the instancing context. **Present means this
|
`:palette` is the symbol's authoring/preview palette. It seeds evaluation only
|
||||||
timeline's content is read in that ramp, and it travels with the timeline** — a
|
when that symbol is the viewed root; nested symbols do not replace the root's
|
||||||
symbol authored against `:night` stays night wherever it is placed. That is
|
choice merely because they were authored under another ramp. When absent, the
|
||||||
lexical scope, and deliberately: a character with their own palette is a
|
project default seeds evaluation.
|
||||||
character, not a decoration of whichever scene they were dropped into.
|
|
||||||
|
|
||||||
Composition is the same walk as `:time` — down the instance chain, **innermost
|
Covered clips of the viewed root's palette track override that seed. An
|
||||||
set palette wins**. An enclosing timeline's palette therefore applies to
|
uncovered lane interval is a genuine gap, restoring the authoring palette or
|
||||||
everything inside it that does not set its own, which is adjustment-layer
|
project default. Palette clips use the same trim, roll, slide, claim-time and
|
||||||
behaviour with no adjustment layer in it. It is just scope.
|
undo commands as visual clips; palette code does not duplicate those edits.
|
||||||
|
Thus palette-track coverage, authoring preview, and project fallback are
|
||||||
And because it is an ordinary channel, a project switches palette over time with
|
separate facts rather than three accidental meanings of one field. There is no
|
||||||
keys on the root timeline, a child timeline switches on its own, and neither
|
second keyed palette control on symbols or instances: time-varying palette
|
||||||
knows about the other.
|
changes are authored only as clips in the palette lane.
|
||||||
|
|
||||||
### One index space, partitioned by palette
|
### One index space, partitioned by palette
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,40 @@
|
||||||
native node frames and world matrices for the last rendered output frame."
|
native node frames and world matrices for the last rendered output frame."
|
||||||
[clip sid store palette opts]
|
[clip sid store palette opts]
|
||||||
(let [context? (and (map? palette) (:palettes palette) (:offsets palette))]
|
(let [context? (and (map? palette) (:palettes palette) (:offsets palette))]
|
||||||
(letfn [(selection-at [owner frame inherited]
|
(letfn [(channel-value [selection frame]
|
||||||
|
(cond
|
||||||
|
(nil? selection) nil
|
||||||
|
(and (map? selection) (contains? selection :animated?))
|
||||||
|
(ch/value-at selection frame store)
|
||||||
|
:else selection))
|
||||||
|
(root-selection-at [owner frame inherited]
|
||||||
|
;; A palette track has meaningful uncovered time. Ordinary held
|
||||||
|
;; channels clamp to their first key before it, but doing that
|
||||||
|
;; here would erase the gap before the first palette segment.
|
||||||
|
(let [track (:palette-channel owner)
|
||||||
|
track-value (if-let [ks (:keys track)]
|
||||||
|
(some->> (keys ks)
|
||||||
|
(filter #(<= % frame))
|
||||||
|
sort last
|
||||||
|
(get ks))
|
||||||
|
(channel-value track frame))]
|
||||||
|
(or (when-let [track-sid (and (keyword? (:palette-track owner))
|
||||||
|
(:palette-track owner))]
|
||||||
|
(let [track-symbol (symbol clip track-sid)
|
||||||
|
palette-clip (first
|
||||||
|
(filter (fn [n]
|
||||||
|
(let [[a b] (node/placed-span n)]
|
||||||
|
(and a (<= a frame) (< frame b))))
|
||||||
|
(symbol/children (:nodes track-symbol))))
|
||||||
|
palette-symbol (some-> palette-clip node/source
|
||||||
|
(#(symbol clip %)))]
|
||||||
|
(when (= :palette (:type palette-symbol))
|
||||||
|
(:palette-ref palette-symbol))))
|
||||||
|
track-value
|
||||||
|
(channel-value (:palette owner) frame)
|
||||||
|
inherited
|
||||||
|
(:default palette))))
|
||||||
|
(selection-at [owner frame inherited]
|
||||||
(let [selection (:palette owner)
|
(let [selection (:palette owner)
|
||||||
chosen (cond
|
chosen (cond
|
||||||
(nil? selection) nil
|
(nil? selection) nil
|
||||||
|
|
@ -257,7 +290,7 @@
|
||||||
(ch/value-at selection frame store)
|
(ch/value-at selection frame store)
|
||||||
:else selection)]
|
:else selection)]
|
||||||
(or chosen inherited (:default palette))))
|
(or chosen inherited (:default palette))))
|
||||||
(build [sid chain pose-tracks]
|
(build [sid chain pose-tracks root?]
|
||||||
(when (some #{sid} chain)
|
(when (some #{sid} chain)
|
||||||
(throw (ex-info "symbol cycle" {:chain (conj chain sid)})))
|
(throw (ex-info "symbol cycle" {:chain (conj chain sid)})))
|
||||||
(let [sym (or (symbol clip sid)
|
(let [sym (or (symbol clip sid)
|
||||||
|
|
@ -276,7 +309,7 @@
|
||||||
:when (= :instance (:kind n))
|
:when (= :instance (:kind n))
|
||||||
child (sort-by str (node/sources n))]
|
child (sort-by str (node/sources n))]
|
||||||
[[id child] (build child (conj chain sid)
|
[[id child] (build child (conj chain sid)
|
||||||
(get-in n [:playback :tracks]))]))
|
(get-in n [:playback :tracks]) false)]))
|
||||||
;; The instances that were on the last frame, and WHICH
|
;; The instances that were on the last frame, and WHICH
|
||||||
;; drawing each was showing — a row path is read back through
|
;; drawing each was showing — a row path is read back through
|
||||||
;; the child that was actually resolved, not the only one
|
;; the child that was actually resolved, not the only one
|
||||||
|
|
@ -286,7 +319,10 @@
|
||||||
step (fn [f pre inherited forced]
|
step (fn [f pre inherited forced]
|
||||||
(when context?
|
(when context?
|
||||||
(vreset! active (or forced
|
(vreset! active (or forced
|
||||||
(selection-at sym (js/Math.floor f) inherited))))
|
(if root?
|
||||||
|
(root-selection-at sym (js/Math.floor f) inherited)
|
||||||
|
inherited)
|
||||||
|
(:default palette))))
|
||||||
(vreset! entered {})
|
(vreset! entered {})
|
||||||
(let [by-id (into {} (map (juxt :node identity))
|
(let [by-id (into {} (map (juxt :node identity))
|
||||||
(own (js/Math.floor f) (js/Math.floor pre)))]
|
(own (js/Math.floor f) (js/Math.floor pre)))]
|
||||||
|
|
@ -349,7 +385,7 @@
|
||||||
(when (contains? @entered id)
|
(when (contains? @entered id)
|
||||||
(symbol/pre-frame-of (get children [id (get @entered id)]) (vec more)))
|
(symbol/pre-frame-of (get children [id (get @entered id)]) (vec more)))
|
||||||
(symbol/pre-frame-of own id))))))]
|
(symbol/pre-frame-of own id))))))]
|
||||||
(let [r (build sid [] nil)
|
(let [r (build sid [] nil true)
|
||||||
grid (or (:grid-fps opts) (:fps clip))
|
grid (or (:grid-fps opts) (:fps clip))
|
||||||
native (fps clip sid)]
|
native (fps clip sid)]
|
||||||
(reify
|
(reify
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,8 @@
|
||||||
;; disagree with itself about; `clip` puts it back.
|
;; disagree with itself about; `clip` puts it back.
|
||||||
(for [[sid sym] (:symbols clip)]
|
(for [[sid sym] (:symbols clip)]
|
||||||
{(at "symbol" (segment sid))
|
{(at "symbol" (segment sid))
|
||||||
(select-keys sym [:name :frames :fps :width :height :palette :display])})
|
(select-keys sym [:name :frames :fps :width :height :palette
|
||||||
|
:palette-track :palette-channel :type :palette-ref :display])})
|
||||||
(for [[sid sym] (:symbols clip)
|
(for [[sid sym] (:symbols clip)
|
||||||
[id n] (:nodes sym)]
|
[id n] (:nodes sym)]
|
||||||
{(at "symbol" (segment sid) "node" (segment id))
|
{(at "symbol" (segment sid) "node" (segment id))
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,8 @@
|
||||||
(nil? n) "nothing to move"
|
(nil? n) "nothing to move"
|
||||||
(or (nil? here) (nil? there)) "both have to be on screen at this frame"
|
(or (nil? here) (nil? there)) "both have to be on screen at this frame"
|
||||||
(nil? (:sid there)) "only a symbol can take it"
|
(nil? (:sid there)) "only a symbol can take it"
|
||||||
|
(span/placement-refusal clip (:sid there) n)
|
||||||
|
(span/placement-refusal clip (:sid there) n)
|
||||||
(not (and (:time here) (:time there)))
|
(not (and (:time here) (:time there)))
|
||||||
"a held or looping clip has no clock to move through"
|
"a held or looping clip has no clock to move through"
|
||||||
(nil? (some-> there :matrix node/invert)) "the target is scaled to nothing"
|
(nil? (some-> there :matrix node/invert)) "the target is scaled to nothing"
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@
|
||||||
used (into #{(default-palette-id clip)}
|
used (into #{(default-palette-id clip)}
|
||||||
(mapcat selection-values)
|
(mapcat selection-values)
|
||||||
(concat (map :palette (vals (:symbols clip)))
|
(concat (map :palette (vals (:symbols clip)))
|
||||||
|
(map :palette-channel (vals (:symbols clip)))
|
||||||
|
(map (fn [sym]
|
||||||
|
(when (= :palette (:type sym)) (:palette-ref sym)))
|
||||||
|
(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))))
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,8 @@
|
||||||
[arthur.domain.wire :as wire]))
|
[arthur.domain.wire :as wire]))
|
||||||
|
|
||||||
(def schema-version
|
(def schema-version
|
||||||
"3 stores cel source references and explicit playback clocks. Older
|
"4 separates a symbol's static authoring palette from its palette track."
|
||||||
source-channel documents are unsupported; there is no compatibility conversion."
|
4)
|
||||||
3)
|
|
||||||
|
|
||||||
(defn block-keys
|
(defn block-keys
|
||||||
"Every tier-2 key a leaf map names, in a stable order."
|
"Every tier-2 key a leaf map names, in a stable order."
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,25 @@
|
||||||
nodes later)]
|
nodes later)]
|
||||||
(finish clip sid nodes id extent)))))
|
(finish clip sid nodes id extent)))))
|
||||||
|
|
||||||
|
(defn placement-refusal
|
||||||
|
"Why direct child `n` may not be placed in symbol `sid`, or nil.
|
||||||
|
|
||||||
|
This is the type-containment boundary for every placement path. Specialized
|
||||||
|
symbols are confined here rather than by drag affordances, so pool drops,
|
||||||
|
transfers, structural nesting and future commands cannot disagree."
|
||||||
|
[clip sid n]
|
||||||
|
(let [destination (clip/symbol clip sid)
|
||||||
|
source (when (= :instance (:kind n)) (clip/symbol clip (node/source n)))
|
||||||
|
destination-type (:type destination)
|
||||||
|
source-type (:type source)]
|
||||||
|
(cond
|
||||||
|
(= :palette destination-type) "a palette symbol cannot contain nodes"
|
||||||
|
(and (= :palette-track destination-type) (not= :palette source-type))
|
||||||
|
"a palette track accepts only palette symbols"
|
||||||
|
(and (= :palette source-type) (not= :palette-track destination-type))
|
||||||
|
"a palette symbol can be placed only in a palette track"
|
||||||
|
:else nil)))
|
||||||
|
|
||||||
(defn place-node
|
(defn place-node
|
||||||
"Place the already-materialized, direct child `n` into symbol `sid` at `at`.
|
"Place the already-materialized, direct child `n` into symbol `sid` at `at`.
|
||||||
|
|
||||||
|
|
@ -519,10 +538,12 @@
|
||||||
nodes (:nodes sym)
|
nodes (:nodes sym)
|
||||||
id (:id n)
|
id (:id n)
|
||||||
[lo hi] (when n (node/placed-span n))
|
[lo hi] (when n (node/placed-span n))
|
||||||
duration (when (and lo hi) (- hi lo))]
|
duration (when (and lo hi) (- hi lo))
|
||||||
|
incompatible (when (and sym n) (placement-refusal clip sid n))]
|
||||||
(cond
|
(cond
|
||||||
(nil? sym) {:refused "there is no destination symbol"}
|
(nil? sym) {:refused "there is no destination symbol"}
|
||||||
(nil? n) {:refused "there is no clip to place"}
|
(nil? n) {:refused "there is no clip to place"}
|
||||||
|
incompatible {:refused incompatible}
|
||||||
(contains? nodes id) {:refused "the destination already uses that clip ID"}
|
(contains? nodes id) {:refused "the destination already uses that clip ID"}
|
||||||
(some? (:parent n)) {:refused "only a direct child can be placed in a symbol"}
|
(some? (:parent n)) {:refused "only a direct child can be placed in a symbol"}
|
||||||
(not (and (integer? at) (not (neg? at))))
|
(not (and (integer? at) (not (neg? at))))
|
||||||
|
|
|
||||||
|
|
@ -709,7 +709,8 @@
|
||||||
`:display` is how the TIMELINE draws the symbol — `:lane` for its clips as
|
`:display` is how the TIMELINE draws the symbol — `:lane` for its clips as
|
||||||
blocks on one row — and is saved because two people editing one document must
|
blocks on one row — and is saved because two people editing one document must
|
||||||
play by the same editing rules. See `lane?`."
|
play by the same editing rules. See `lane?`."
|
||||||
#{:id :name :frames :fps :width :height :nodes :palette :display})
|
#{:id :name :frames :fps :width :height :nodes :palette :palette-track
|
||||||
|
:palette-channel :type :palette-ref :display})
|
||||||
|
|
||||||
(defn problems
|
(defn problems
|
||||||
"Human-readable reasons this symbol will not evaluate. Empty means it will.
|
"Human-readable reasons this symbol will not evaluate. Empty means it will.
|
||||||
|
|
@ -727,6 +728,11 @@
|
||||||
(if-not (map? nodes)
|
(if-not (map? nodes)
|
||||||
[":nodes must be a map of id -> node"]
|
[":nodes must be a map of id -> node"]
|
||||||
(-> []
|
(-> []
|
||||||
|
(cond->
|
||||||
|
(and (= :palette (:type sym)) (seq nodes))
|
||||||
|
(conj "a palette symbol cannot contain nodes")
|
||||||
|
(and (= :palette (:type sym)) (nil? (:palette-ref sym)))
|
||||||
|
(conj "a palette symbol must point at a palette"))
|
||||||
(into (for [[id n] nodes
|
(into (for [[id n] nodes
|
||||||
:when (not= id (:id n))]
|
:when (not= id (:id n))]
|
||||||
(str "node under key " (pr-str id) " has :id " (pr-str (:id n)))))
|
(str "node under key " (pr-str id) " has :id " (pr-str (:id n)))))
|
||||||
|
|
|
||||||
|
|
@ -714,39 +714,49 @@
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::symbol-palette
|
::symbol-palette
|
||||||
(fn [db [_ sid id frame]]
|
(fn [db [_ sid id]]
|
||||||
(edit/edit db #(if id
|
(edit/edit db #(if id
|
||||||
(let [old (get-in % [:symbols sid :palette])]
|
(assoc-in % [:symbols sid :palette] id)
|
||||||
(assoc-in % [:symbols sid :palette]
|
|
||||||
(if (:keys old)
|
|
||||||
(assoc-in old [:keys frame] id)
|
|
||||||
(ch/framed id))))
|
|
||||||
(update-in % [:symbols sid] dissoc :palette)))))
|
(update-in % [:symbols sid] dissoc :palette)))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::key-symbol-palette
|
::drop-palette
|
||||||
(fn [db [_ sid frame id]]
|
(fn [db [_ root-sid frame palette-id]]
|
||||||
(edit/edit db
|
(let [{document :clip st :store} (store/entry (:clip/current db))
|
||||||
(fn [c]
|
root (clip/symbol document root-sid)
|
||||||
(let [old (get-in c [:symbols sid :palette])
|
old-track (:palette-track root)
|
||||||
base (or (when old (ch/value-at old frame nil))
|
track-id (if (= :palette-track (get-in document [:symbols old-track :type]))
|
||||||
(pal/default-palette-id c))
|
old-track (clip/fresh-id document))
|
||||||
keyed (if (:keys old) old (ch/keyed {0 base} :hold))]
|
palette-sid (or (some (fn [[sid sym]]
|
||||||
(assoc-in c [:symbols sid :palette]
|
(when (and (= :palette (:type sym))
|
||||||
(assoc-in keyed [:keys frame] id)))))))
|
(= palette-id (:palette-ref sym))) sid))
|
||||||
|
(:symbols document))
|
||||||
(rf/reg-event-db
|
(clip/fresh-id (cond-> document
|
||||||
::instance-palette
|
(not= track-id old-track)
|
||||||
(fn [db [_ sid node-id frame id]]
|
(assoc-in [:symbols track-id] {}))))
|
||||||
(edit/edit db
|
document (cond-> (if (= track-id old-track)
|
||||||
(fn [c]
|
document
|
||||||
(if-not id
|
(-> document
|
||||||
(update-in c [:symbols sid :nodes node-id] dissoc :palette)
|
(assoc-in [:symbols root-sid :palette-track] track-id)
|
||||||
(let [old (get-in c [:symbols sid :nodes node-id :palette])]
|
(assoc-in [:symbols track-id]
|
||||||
(assoc-in c [:symbols sid :nodes node-id :palette]
|
{:id track-id :name "palette" :type :palette-track
|
||||||
(if (:keys old)
|
:display :lane :frames (:frames root)
|
||||||
(assoc-in old [:keys frame] id)
|
:fps (clip/fps document root-sid) :nodes {}})))
|
||||||
(ch/framed id)))))))))
|
(nil? (clip/symbol document palette-sid))
|
||||||
|
(assoc-in [:symbols palette-sid]
|
||||||
|
{:id palette-sid
|
||||||
|
:name (or (get-in document [:palettes palette-id :name])
|
||||||
|
(name palette-id))
|
||||||
|
:type :palette :palette-ref palette-id
|
||||||
|
:frames 1 :fps (clip/fps document root-sid) :nodes {}}))
|
||||||
|
id (random-uuid)
|
||||||
|
result (span/place-symbol document st track-id id palette-sid frame
|
||||||
|
{:extent :grow-symbol :remainder-id (random-uuid)})]
|
||||||
|
(if-let [why (:refused result)]
|
||||||
|
(update db :project merge {:status why})
|
||||||
|
(-> db
|
||||||
|
(edit/edit (constantly (:clip result)))
|
||||||
|
(assoc-in [:ui :selection] [:node track-id id [id]]))))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::set-channel
|
::set-channel
|
||||||
|
|
|
||||||
|
|
@ -855,18 +855,18 @@
|
||||||
::sliding
|
::sliding
|
||||||
;; A bar in the middle of a slide, drawn by `::render/clip`; nil path when the
|
;; A bar in the middle of a slide, drawn by `::render/clip`; nil path when the
|
||||||
;; drag is abandoned.
|
;; drag is abandoned.
|
||||||
(fn [db [_ path df kind ripple? other]]
|
(fn [db [_ path df kind ripple? other owner]]
|
||||||
(if path
|
(if path
|
||||||
(assoc-in db [:ui :sliding] {:path path :df df :kind (or kind :slide)
|
(assoc-in db [:ui :sliding] {:path path :df df :kind (or kind :slide)
|
||||||
:ripple? (boolean ripple?) :other other})
|
:ripple? (boolean ripple?) :other other :owner owner})
|
||||||
(update db :ui dissoc :sliding))))
|
(update db :ui dissoc :sliding))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::slide
|
::slide
|
||||||
(fn [db [_ path df kind ripple? other]]
|
(fn [db [_ path df kind ripple? other owner]]
|
||||||
(let [db (update db :ui dissoc :sliding)
|
(let [db (update db :ui dissoc :sliding)
|
||||||
clip (:clip (store/entry (:clip/current db)))
|
clip (:clip (store/entry (:clip/current db)))
|
||||||
open (get-in db [:ui :open])
|
open (or owner (get-in db [:ui :open]))
|
||||||
r (case kind
|
r (case kind
|
||||||
:out (nest/resize-out clip open path df ripple?)
|
:out (nest/resize-out clip open path df ripple?)
|
||||||
:in (nest/resize-in clip open path df)
|
:in (nest/resize-in clip open path df)
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,13 @@
|
||||||
;; lets go, so the stage and the rows follow the pointer. Nothing is written
|
;; lets go, so the stage and the rows follow the pointer. Nothing is written
|
||||||
;; until then: one drag is one undo step and one write to collaborators.
|
;; until then: one drag is one undo step and one write to collaborators.
|
||||||
(let [{c :clip st :store} (footage/entry id)]
|
(let [{c :clip st :store} (footage/entry id)]
|
||||||
(or (when-let [{:keys [path df kind ripple? other]} sliding]
|
(or (when-let [{:keys [path df kind ripple? other owner]} sliding]
|
||||||
(:clip (case kind
|
(let [root (or owner open)]
|
||||||
:out (nest/resize-out c open path df ripple?)
|
(:clip (case kind
|
||||||
:in (nest/resize-in c open path df)
|
:out (nest/resize-out c root path df ripple?)
|
||||||
:roll (nest/roll c open other path df)
|
:in (nest/resize-in c root path df)
|
||||||
(nest/slide c open path df))))
|
:roll (nest/roll c root other path df)
|
||||||
|
(nest/slide c root path df)))))
|
||||||
(when-let [{:keys [sid id frame values edits]} gesture]
|
(when-let [{:keys [sid id frame values edits]} gesture]
|
||||||
;; A held stage control owns the touched parameters completely. Make
|
;; A held stage control owns the touched parameters completely. Make
|
||||||
;; them temporary static channels for the preview, so their existing
|
;; them temporary static channels for the preview, so their existing
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,13 @@
|
||||||
:center (clip/center document st sid)
|
:center (clip/center document st sid)
|
||||||
:shapes (outline document st sid)}))))
|
:shapes (outline document st sid)}))))
|
||||||
|
|
||||||
|
(defn palette!
|
||||||
|
"Start carrying a project palette toward the open symbol's palette track."
|
||||||
|
[id label]
|
||||||
|
(reset! carrying {:kind :palette :palette id :label label :frames 1}))
|
||||||
|
|
||||||
|
(defn palette? [] (= :palette (:kind @carrying)))
|
||||||
|
|
||||||
(defn accepts?
|
(defn accepts?
|
||||||
"Whether the stage or the tracks should accept what is being carried: things
|
"Whether the stage or the tracks should accept what is being carried: things
|
||||||
out of the pool, and not one that would make a cycle."
|
out of the pool, and not one that would make a cycle."
|
||||||
|
|
@ -120,3 +127,10 @@
|
||||||
:sound (rf/dispatch [::ui/drop-sound c frame target])
|
:sound (rf/dispatch [::ui/drop-sound c frame target])
|
||||||
nil))
|
nil))
|
||||||
(done!)))
|
(done!)))
|
||||||
|
|
||||||
|
(defn land-palette!
|
||||||
|
"Put the carried palette on `sid`'s palette track at `frame`."
|
||||||
|
[sid frame]
|
||||||
|
(when-let [{:keys [palette]} (when (palette?) @carrying)]
|
||||||
|
(rf/dispatch [::project/drop-palette sid frame palette]))
|
||||||
|
(done!))
|
||||||
|
|
|
||||||
|
|
@ -218,16 +218,6 @@
|
||||||
"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)))]
|
||||||
(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])])
|
||||||
(when (= :instance (:kind n))
|
|
||||||
[:label.inspector-field "palette override"
|
|
||||||
[:select {:value (str (or (some-> (:palette n) (channel/value-at frame nil)) ""))
|
|
||||||
:on-change (fn [e]
|
|
||||||
(let [v (.. e -target -value)
|
|
||||||
pid (first (filter #(= v (str %)) (keys (pal/palettes clip))))]
|
|
||||||
(rf/dispatch [::project/instance-palette sid id frame pid])))}
|
|
||||||
[:option {:value ""} "inherit"]
|
|
||||||
(for [[pid p] (sort-by (comp str :name val) (pal/palettes clip))]
|
|
||||||
^{:key (str pid)} [:option {:value (str pid)} (:name p)])]])
|
|
||||||
[: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]
|
||||||
[:dl.facts
|
[:dl.facts
|
||||||
|
|
@ -538,14 +528,12 @@
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; a symbol
|
;; a symbol
|
||||||
|
|
||||||
(defn- symbol-section [sid]
|
(defn- symbol-section [sid & [title]]
|
||||||
(let [clip @(rf/subscribe [::render/clip])
|
(let [clip @(rf/subscribe [::render/clip])
|
||||||
sym (get-in clip [:symbols sid])
|
sym (get-in clip [:symbols sid])
|
||||||
frame @(rf/subscribe [::render/open-frame])
|
frame @(rf/subscribe [::render/open-frame])
|
||||||
palette-value (or (some-> (:palette sym) (channel/value-at frame nil))
|
|
||||||
(pal/default-palette-id clip))
|
|
||||||
busy? (:busy? @(rf/subscribe [::playback/project]))]
|
busy? (:busy? @(rf/subscribe [::playback/project]))]
|
||||||
[section "symbol"
|
[section (or title "symbol")
|
||||||
[facts
|
[facts
|
||||||
"id" (str sid)
|
"id" (str sid)
|
||||||
"length" (str (:frames sym) " frames at " (clip-domain/fps clip sid) " fps")
|
"length" (str (:frames sym) " frames at " (clip-domain/fps clip sid) " fps")
|
||||||
|
|
@ -563,23 +551,15 @@
|
||||||
#(rf/dispatch [::project/symbol-setting sid :width %]) "project default" busy?]
|
#(rf/dispatch [::project/symbol-setting sid :width %]) "project default" busy?]
|
||||||
[number-field "height" (:height sym)
|
[number-field "height" (:height sym)
|
||||||
#(rf/dispatch [::project/symbol-setting sid :height %]) "project default" busy?]
|
#(rf/dispatch [::project/symbol-setting sid :height %]) "project default" busy?]
|
||||||
[:label.inspector-field "palette"
|
[:label.inspector-field "authoring palette"
|
||||||
[:select {:value (str palette-value) :disabled busy?
|
[:select {:value (str (or (:palette sym) "")) :disabled busy?
|
||||||
:on-change (fn [e]
|
:on-change (fn [e]
|
||||||
(let [v (.. e -target -value)
|
(let [v (.. e -target -value)
|
||||||
id (first (filter #(= v (str %)) (keys (pal/palettes clip))))]
|
id (first (filter #(= v (str %)) (keys (pal/palettes clip))))]
|
||||||
(rf/dispatch [::project/symbol-palette sid id frame])))}
|
(rf/dispatch [::project/symbol-palette sid id])))}
|
||||||
[:option {:value ""} "inherit"]
|
[:option {:value ""} "inherit"]
|
||||||
(for [[id p] (sort-by (comp str :name val) (pal/palettes clip))]
|
(for [[id p] (sort-by (comp str :name val) (pal/palettes clip))]
|
||||||
^{:key (str id)} [:option {:value (str id)} (:name p)])]]
|
^{:key (str id)} [:option {:value (str id)} (:name p)])]]
|
||||||
[:div.row
|
|
||||||
[:button {:disabled busy?
|
|
||||||
:title "hold this palette from this frame"
|
|
||||||
:on-click #(rf/dispatch [::project/key-symbol-palette sid frame palette-value])}
|
|
||||||
"◆ key palette"]
|
|
||||||
[:button {:disabled (or busy? (nil? (:palette sym)))
|
|
||||||
:on-click #(rf/dispatch [::project/symbol-palette sid nil])}
|
|
||||||
"inherit"]]
|
|
||||||
(when (or (:width sym) (:height sym))
|
(when (or (:width sym) (:height sym))
|
||||||
[:div.row
|
[:div.row
|
||||||
[:button {:disabled busy? :on-click #(do
|
[:button {:disabled busy? :on-click #(do
|
||||||
|
|
@ -587,6 +567,28 @@
|
||||||
(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
|
||||||
;;
|
;;
|
||||||
|
|
@ -706,6 +708,11 @@
|
||||||
[:div {:style {:min-height 0}}
|
[:div {:style {:min-height 0}}
|
||||||
[clip-section]
|
[clip-section]
|
||||||
(when node [node-section node])
|
(when node [node-section node])
|
||||||
|
(when placed [symbol-section placed "source symbol"])
|
||||||
|
(when placed
|
||||||
|
(let [{source-sid :sid source-frame :frame} @(rf/subscribe [::sub/selected-local])]
|
||||||
|
(when (= placed source-sid)
|
||||||
|
[symbol-parameters-section placed source-frame "source parameters"])))
|
||||||
(when node ^{:key (str (first node) "/" (second node))}
|
(when node ^{:key (str (first node) "/" (second node))}
|
||||||
[correction-section node])
|
[correction-section node])
|
||||||
(when (seq here) [footage-section here])
|
(when (seq here) [footage-section here])
|
||||||
|
|
@ -713,4 +720,9 @@
|
||||||
[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 (= :symbol (first selection)) [symbol-section (second selection)])
|
||||||
|
(when (= :symbol (first selection))
|
||||||
|
(let [sid (second selection)
|
||||||
|
frame (min (max 0 @(rf/subscribe [::render/open-frame]))
|
||||||
|
(max 0 (dec (or (clip-domain/frames clip sid) 1))))]
|
||||||
|
[symbol-parameters-section sid frame "symbol parameters"]))
|
||||||
(when tracked? [tracking-section])]]))
|
(when tracked? [tracking-section])]]))
|
||||||
|
|
|
||||||
|
|
@ -272,9 +272,10 @@
|
||||||
|
|
||||||
(defn- palette-row [id p default-id chosen rename]
|
(defn- palette-row [id p default-id chosen rename]
|
||||||
^{:key (str id)}
|
^{:key (str id)}
|
||||||
[row {:label (:name p)
|
[row (merge {:label (:name p)
|
||||||
:sub (str (count (:slots p)) " colors")
|
:sub (str (count (:slots p)) " colors")
|
||||||
:title (str (:name p) " · " (count (:slots p)) " indexed colors")
|
:title (str (:name p) " · " (count (:slots p))
|
||||||
|
" indexed colors — drag onto the palette track")
|
||||||
:thumb [:span.thumb {:style {:display "grid"
|
:thumb [:span.thumb {:style {:display "grid"
|
||||||
:grid-template-columns "repeat(4,1fr)"}}
|
:grid-template-columns "repeat(4,1fr)"}}
|
||||||
(for [[i s] (map-indexed vector (take 16 (:slots p)))]
|
(for [[i s] (map-indexed vector (take 16 (:slots p)))]
|
||||||
|
|
@ -285,7 +286,8 @@
|
||||||
((:begin! rename) nil)
|
((:begin! rename) nil)
|
||||||
(rf/dispatch [::project/palette-name id value])))
|
(rf/dispatch [::project/palette-name id value])))
|
||||||
:on-click #(rf/dispatch [:arthur.ui.palette/select id])
|
:on-click #(rf/dispatch [:arthur.ui.palette/select id])
|
||||||
: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))))])
|
||||||
|
|
||||||
(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}
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,18 @@
|
||||||
(walk sid [] 0 identity)
|
(walk sid [] 0 identity)
|
||||||
[])))))
|
[])))))
|
||||||
|
|
||||||
|
(defn- palette-track-row [clip sid frames expanded chosen]
|
||||||
|
(let [track-id (get-in clip [:symbols sid :palette-track])
|
||||||
|
track (get-in clip [:symbols track-id])]
|
||||||
|
(if (= :palette-track (:type track))
|
||||||
|
(-> (first (rows clip track-id expanded chosen))
|
||||||
|
(assoc :path [::palette-track] :owner track-id
|
||||||
|
:label "palette" :kind :palette
|
||||||
|
:select [::palette-track sid]))
|
||||||
|
{:path [::palette-track] :depth 0 :label "palette" :kind :palette
|
||||||
|
:owner nil :select [::palette-track sid]
|
||||||
|
:span [0 frames] :keys [] :cels []})))
|
||||||
|
|
||||||
(defn sound-rows
|
(defn sound-rows
|
||||||
"Audio rows use the same flattened intervals as the mixer, including source
|
"Audio rows use the same flattened intervals as the mixer, including source
|
||||||
in-points, cel speeds, parent timing, and silence beneath visual holds.
|
in-points, cel speeds, parent timing, and silence beneath visual holds.
|
||||||
|
|
@ -705,7 +717,7 @@
|
||||||
`:from` is the frame the press was on and `:df` how many frames the pointer has
|
`:from` is the frame the press was on and `:df` how many frames the pointer has
|
||||||
moved since. What it looks like mid-drag is `[:ui :sliding]`, which the clip
|
moved since. What it looks like mid-drag is `[:ui :sliding]`, which the clip
|
||||||
every row and the stage are drawn from already has in it."
|
every row and the stage are drawn from already has in it."
|
||||||
[{:keys [path span keys dense? kind node-kind select slides cels lane? of unmapped?]}
|
[{:keys [path span keys dense? kind node-kind select slides cels lane? of unmapped? owner]}
|
||||||
frames sliding hint {:keys [clip store open frame selection]}]
|
frames sliding hint {:keys [clip store open frame selection]}]
|
||||||
(let [active-row (:row @sliding)
|
(let [active-row (:row @sliding)
|
||||||
slide (fn [^js e]
|
slide (fn [^js e]
|
||||||
|
|
@ -784,7 +796,7 @@
|
||||||
(swap! sliding assoc :df df)
|
(swap! sliding assoc :df df)
|
||||||
(rf/dispatch [::ui/sliding (:path @sliding) df
|
(rf/dispatch [::ui/sliding (:path @sliding) df
|
||||||
(:kind @sliding) (:ripple? @sliding)
|
(:kind @sliding) (:ripple? @sliding)
|
||||||
(:other @sliding)]))))))))
|
(:other @sliding) owner]))))))))
|
||||||
done (fn [commit?]
|
done (fn [commit?]
|
||||||
(when (= path (:row @sliding))
|
(when (= path (:row @sliding))
|
||||||
(let [{:keys [path df kind ripple? other target-lane target-frame
|
(let [{:keys [path df kind ripple? other target-lane target-frame
|
||||||
|
|
@ -808,7 +820,7 @@
|
||||||
;; selected. The two structural cases above select what
|
;; selected. The two structural cases above select what
|
||||||
;; they landed, so neither needs this.
|
;; they landed, so neither needs this.
|
||||||
(do (when on-click (rf/dispatch [::ui/select on-click]))
|
(do (when on-click (rf/dispatch [::ui/select on-click]))
|
||||||
(rf/dispatch [::ui/slide path df kind ripple? other]))
|
(rf/dispatch [::ui/slide path df kind ripple? other owner]))
|
||||||
:else (rf/dispatch [::ui/sliding nil])))))
|
:else (rf/dispatch [::ui/sliding nil])))))
|
||||||
begin! (fn [^js e actual-path gesture-kind actual-select other drag]
|
begin! (fn [^js e actual-path gesture-kind actual-select other drag]
|
||||||
(let [track (.closest (.-currentTarget e) ".tl-track")]
|
(let [track (.closest (.-currentTarget e) ".tl-track")]
|
||||||
|
|
@ -839,7 +851,7 @@
|
||||||
[:div.tl-track
|
[:div.tl-track
|
||||||
;; The track, not the bar, holds the pointer while a bar slides, so the drag
|
;; The track, not the bar, holds the pointer while a bar slides, so the drag
|
||||||
;; goes on when the bar has slid off the ruler and is no longer drawn.
|
;; goes on when the bar has slid off the ruler and is no longer drawn.
|
||||||
{:class (when lane? "lane")
|
{:class (str (when lane? "lane") (when (= :palette kind) " palette"))
|
||||||
:on-pointer-move slide
|
:on-pointer-move slide
|
||||||
:on-pointer-up (fn [e] (slide e) (done true))
|
:on-pointer-up (fn [e] (slide e) (done true))
|
||||||
:on-pointer-cancel (fn [_] (done false))
|
:on-pointer-cancel (fn [_] (done false))
|
||||||
|
|
@ -854,28 +866,40 @@
|
||||||
(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]
|
||||||
(when (and lane? (or (drag/accepts?) (drag/row)))
|
(cond
|
||||||
(.preventDefault e)
|
(and (= :palette kind) (drag/palette?))
|
||||||
(.stopPropagation e)
|
(do (.preventDefault e) (.stopPropagation e))
|
||||||
(when (drag/accepts?)
|
(and (not= :palette kind) lane?
|
||||||
(drag/hover! :timeline (frame-at e frames) nil select))))
|
(or (drag/accepts?) (drag/row)))
|
||||||
|
(do (.preventDefault e) (.stopPropagation e))))
|
||||||
:on-drag-over (fn [^js e]
|
:on-drag-over (fn [^js e]
|
||||||
(when (and lane? (or (drag/accepts?) (drag/row)))
|
(cond
|
||||||
(.preventDefault e)
|
(and (= :palette kind) (drag/palette?))
|
||||||
(.stopPropagation e)
|
(do (.preventDefault e) (.stopPropagation e)
|
||||||
(set! (.. e -dataTransfer -dropEffect)
|
(set! (.. e -dataTransfer -dropEffect) "copy")
|
||||||
(if (drag/row) "move" "copy"))
|
(drag/hover! :timeline (frame-at e frames) nil select))
|
||||||
(when (drag/accepts?)
|
(and (not= :palette kind) lane?
|
||||||
(drag/hover! :timeline (frame-at e frames) nil select))))
|
(or (drag/accepts?) (drag/row)))
|
||||||
|
(do (.preventDefault e) (.stopPropagation e)
|
||||||
|
(set! (.. e -dataTransfer -dropEffect)
|
||||||
|
(if (drag/row) "move" "copy"))
|
||||||
|
(when (drag/accepts?)
|
||||||
|
(drag/hover! :timeline (frame-at e frames) nil select)))))
|
||||||
:on-drop (fn [^js e]
|
:on-drop (fn [^js e]
|
||||||
(when (and lane? (or (drag/accepts?) (drag/row)))
|
(cond
|
||||||
(.preventDefault e)
|
(and (= :palette kind) (drag/palette?))
|
||||||
(.stopPropagation e)
|
(let [at (frame-at e frames)]
|
||||||
(if-let [from (drag/row-selection)]
|
(.preventDefault e) (.stopPropagation e)
|
||||||
(do (drag/done!)
|
(drag/land-palette! open at))
|
||||||
(rf/dispatch [::ui/drop-clip from select
|
(and (not= :palette kind) lane?
|
||||||
(frame-at e frames)]))
|
(or (drag/accepts?) (drag/row)))
|
||||||
(drag/land! (frame-at e frames) nil select))))}
|
(do (.preventDefault e)
|
||||||
|
(.stopPropagation e)
|
||||||
|
(if-let [from (drag/row-selection)]
|
||||||
|
(do (drag/done!)
|
||||||
|
(rf/dispatch [::ui/drop-clip from select
|
||||||
|
(frame-at e frames)]))
|
||||||
|
(drag/land! (frame-at e frames) nil select)))))}
|
||||||
;; Clipped to the ruler: an instance longer than the room left in its
|
;; Clipped to the ruler: an instance longer than the room left in its
|
||||||
;; symbol still plays its own frames from 0, it is just cut off at the end.
|
;; symbol still plays its own frames from 0, it is just cut off at the end.
|
||||||
(when-let [[in out] (when (and span (nil? cels)) [(max 0 (first span)) (min frames (second span))])]
|
(when-let [[in out] (when (and span (nil? cels)) [(max 0 (first span)) (min frames (second span))])]
|
||||||
|
|
@ -1052,13 +1076,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->> (rows clip open expanded chosen)
|
picture (cond->> (cond->> (into [(palette-track-row clip open frames 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))))
|
||||||
(and ghost (not (:sound? drop))) (cons ghost))
|
;; 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.
|
||||||
|
(and ghost (not (:sound? drop)))
|
||||||
|
((fn [rows] (into [(first rows) ghost] (rest rows)))))
|
||||||
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))
|
||||||
|
|
|
||||||
|
|
@ -318,3 +318,40 @@
|
||||||
"the inheriting child follows the lightning cut; an instance override wins")
|
"the inheriting child follows the lightning cut; an instance override wins")
|
||||||
(is (= [17 34 51] (nth (:ramp context) 1)))
|
(is (= [17 34 51] (nth (:ramp context) 1)))
|
||||||
(is (= [170 187 204] (nth (:ramp context) 3)))))
|
(is (= [170 187 204] (nth (:ramp context) 3)))))
|
||||||
|
|
||||||
|
(deftest symbol-authoring-palettes-seed-only-the-view-root
|
||||||
|
(let [palette (fn [id name color]
|
||||||
|
{:id id :name name :slots [{:hex color} {:hex color}]})
|
||||||
|
mark {:id :mark :kind :rect :z "a1"
|
||||||
|
:channels {[:geom :size] (ch/framed 2)
|
||||||
|
[:style :color] (ch/framed 1)}}
|
||||||
|
document {:fps 30 :width 20 :height 20
|
||||||
|
:palettes {:day (palette :day "Day" "#112233")
|
||||||
|
:night (palette :night "Night" "#aabbcc")}
|
||||||
|
:default-palette :day
|
||||||
|
:symbols
|
||||||
|
{:main {:id :main :frames 4 :palette :day :palette-track :palette-lane
|
||||||
|
:nodes {:child {:id :child :kind :instance :z "a1"
|
||||||
|
:source {:symbol :drawing}}}}
|
||||||
|
:palette-lane {:id :palette-lane :name "palette"
|
||||||
|
:type :palette-track :display :lane :frames 4
|
||||||
|
:nodes {:night {:id :night :kind :instance :z "a1"
|
||||||
|
:source {:symbol :night-palette}
|
||||||
|
:span [0 1] :time {:at 1}}}}
|
||||||
|
:night-palette {:id :night-palette :name "Night"
|
||||||
|
:type :palette :palette-ref :night
|
||||||
|
:frames 1 :nodes {}}
|
||||||
|
:drawing {:id :drawing :frames 4 :palette :night
|
||||||
|
:nodes {:mark mark}}}}
|
||||||
|
context (pal/compile document)
|
||||||
|
main (clip/resolver document :main nil context nil)
|
||||||
|
drawing (clip/resolver document :drawing nil context nil)
|
||||||
|
color #(-> (%1 %2) first :color)]
|
||||||
|
(is (= (pal/render-index context :day 1) (color main 0))
|
||||||
|
"a nested symbol's authoring palette does not override the viewed root")
|
||||||
|
(is (= (pal/render-index context :night 1) (color main 1))
|
||||||
|
"a covered palette-track interval overrides the root fallback")
|
||||||
|
(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")))
|
||||||
|
|
|
||||||
|
|
@ -90,3 +90,26 @@
|
||||||
(deftest host-frame-of-a-parentless-clip-is-the-symbol-frame
|
(deftest host-frame-of-a-parentless-clip-is-the-symbol-frame
|
||||||
(is (= 6 (span/host-frame (document) :main :b 6)))
|
(is (= 6 (span/host-frame (document) :main :b 6)))
|
||||||
(is (= 6 (span/host-frame (ordinary-document) :main :badge 6))))
|
(is (= 6 (span/host-frame (ordinary-document) :main :badge 6))))
|
||||||
|
|
||||||
|
(deftest typed-symbol-containment-applies-to-every-transfer
|
||||||
|
(let [palette-node {:id :p :kind :instance :z "a1"
|
||||||
|
:source {:symbol :night-palette}
|
||||||
|
:span [0 1] :time {:at 0}}
|
||||||
|
visual-node {:id :v :kind :instance :z "a1"
|
||||||
|
:source {:symbol :drawing}
|
||||||
|
:span [0 1] :time {:at 0}}
|
||||||
|
doc {:fps 30 :width 20 :height 20
|
||||||
|
:symbols
|
||||||
|
{:root {:id :root :frames 8 :nodes {}}
|
||||||
|
:ordinary-lane {:id :ordinary-lane :frames 8 :display :lane :nodes {}}
|
||||||
|
:palette-lane {:id :palette-lane :frames 8 :display :lane
|
||||||
|
:type :palette-track :nodes {:p palette-node}}
|
||||||
|
:night-palette {:id :night-palette :frames 1 :type :palette
|
||||||
|
:palette-ref :night :nodes {}}
|
||||||
|
:drawing {:id :drawing :frames 1 :nodes {}}}}]
|
||||||
|
(is (:refused (span/transfer doc :palette-lane :p :root 0 {}))
|
||||||
|
"a palette clip cannot escape to a composition root")
|
||||||
|
(is (:refused (span/transfer doc :palette-lane :p :ordinary-lane 0 {}))
|
||||||
|
"a palette clip cannot escape to an ordinary lane")
|
||||||
|
(is (:refused (span/place-node doc :palette-lane visual-node 0 {}))
|
||||||
|
"a palette lane refuses ordinary visual clips")))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue