Unify root timing and persistent lane targets
This commit is contained in:
parent
f5a39aee39
commit
1b2b4ad3d2
11 changed files with 175 additions and 33 deletions
|
|
@ -4,6 +4,10 @@ Project `:fps` is the playback and export grid. Each symbol has its own native
|
||||||
`:fps` and `:frames`; keys, spans, trace choices and corrections stay in that
|
`:fps` and `:frames`; keys, spans, trace choices and corrections stay in that
|
||||||
native space. A symbol without an explicit rate inherits the document rate;
|
native space. A symbol without an explicit rate inherits the document rate;
|
||||||
changing project fps first records that rate so its existing timing stays put.
|
changing project fps first records that rate so its existing timing stays put.
|
||||||
|
The untouched symbol in a new document is deliberately different: it has no
|
||||||
|
authored timing to preserve, so it stays on the project grid and its empty frame
|
||||||
|
extent is rescaled to keep the same duration. This makes changing fps before
|
||||||
|
authoring establish the editor's grid instead of preserving the 30fps default.
|
||||||
|
|
||||||
An output frame selects the latest native frame at or before its time:
|
An output frame selects the latest native frame at or before its time:
|
||||||
`floor(output-frame * native-fps / output-fps)`. Thus 30fps content in a 12fps
|
`floor(output-frame * native-fps / output-fps)`. Thus 30fps content in a 12fps
|
||||||
|
|
|
||||||
|
|
@ -71,12 +71,25 @@
|
||||||
(defn fps [clip sid] (or (:fps (symbol clip sid)) (:fps clip)))
|
(defn fps [clip sid] (or (:fps (symbol clip sid)) (:fps clip)))
|
||||||
|
|
||||||
(defn set-fps
|
(defn set-fps
|
||||||
"Change the output grid without rewriting any content's frames."
|
"Change the output grid without rewriting authored content's frames.
|
||||||
|
|
||||||
|
A new document's empty symbol is the one exception: it has no native rate yet,
|
||||||
|
so it follows the project grid and its empty extent is rescaled to preserve its
|
||||||
|
duration. Once a symbol contains anything, changing the project rate records
|
||||||
|
the old effective rate on it before changing the output grid."
|
||||||
[clip rate]
|
[clip rate]
|
||||||
(-> clip
|
(let [old (:fps clip)]
|
||||||
(update :symbols #(into {} (map (fn [[sid sym]]
|
(-> clip
|
||||||
[sid (assoc sym :fps (fps clip sid))])) %))
|
(update :symbols
|
||||||
(assoc :fps rate)))
|
#(into {}
|
||||||
|
(map (fn [[sid sym]]
|
||||||
|
[sid (cond
|
||||||
|
(:fps sym) sym
|
||||||
|
(empty? (:nodes sym))
|
||||||
|
(update sym :frames cadence/frames rate old)
|
||||||
|
:else (assoc sym :fps old))]))
|
||||||
|
%))
|
||||||
|
(assoc :fps rate))))
|
||||||
|
|
||||||
(defn output-frames [clip sid]
|
(defn output-frames [clip sid]
|
||||||
(cadence/frames (frames clip sid) (:fps clip) (fps clip sid)))
|
(cadence/frames (frames clip sid) (:fps clip) (fps clip sid)))
|
||||||
|
|
@ -165,6 +178,18 @@
|
||||||
(first (sort-by (fn [sid] [(- (or (frames clip sid) 0)) (str sid)])
|
(first (sort-by (fn [sid] [(- (or (frames clip sid) 0)) (str sid)])
|
||||||
(unplaced clip))))
|
(unplaced clip))))
|
||||||
|
|
||||||
|
(defn set-root-fps
|
||||||
|
"Set the document/output rate and the root symbol's editing rate together.
|
||||||
|
|
||||||
|
Project FPS is the root timeline's clock. Nested symbols keep their own native
|
||||||
|
rates and are sampled when placed across that boundary; only the root changes
|
||||||
|
here. Frame numbers are authored positions, so changing the rate does not
|
||||||
|
rewrite them or silently move cuts and keys."
|
||||||
|
[clip rate]
|
||||||
|
(let [root (opens-on clip)]
|
||||||
|
(cond-> (assoc clip :fps rate)
|
||||||
|
root (assoc-in [:symbols root :fps] rate))))
|
||||||
|
|
||||||
(def ^:const blank-frames
|
(def ^:const blank-frames
|
||||||
"How long a new document is before anything says otherwise. Four seconds at 30,
|
"How long a new document is before anything says otherwise. Four seconds at 30,
|
||||||
which is long enough to key something into and short enough to scrub by hand."
|
which is long enough to key something into and short enough to scrub by hand."
|
||||||
|
|
@ -188,7 +213,9 @@
|
||||||
{:name "untitled"
|
{:name "untitled"
|
||||||
:fps 30
|
:fps 30
|
||||||
:width 320 :height 200
|
:width 320 :height 200
|
||||||
:symbols {:main {:id :main :fps 30 :frames blank-frames :nodes {}}}})
|
;; No native fps yet: an untouched canvas follows the project grid. Imported
|
||||||
|
;; and generated symbols carry their own rate explicitly.
|
||||||
|
:symbols {:main {:id :main :frames blank-frames :nodes {}}}})
|
||||||
|
|
||||||
(defn- transform-op
|
(defn- transform-op
|
||||||
"Put a symbol's already resolved mark into its instance's parent space. Its
|
"Put a symbol's already resolved mark into its instance's parent space. Its
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,28 @@
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.symbol :as symbol]))
|
[arthur.domain.symbol :as symbol]))
|
||||||
|
|
||||||
|
(defn- fit-lanes
|
||||||
|
"Make direct lane children cover `sid`'s authored window.
|
||||||
|
|
||||||
|
A lane has no independently authored extent: it is a view of its parent
|
||||||
|
symbol's timeline. Keep that invariant at the one commit point that can grow
|
||||||
|
a symbol, so neither the wrapper nor the lane symbol retains an old parent
|
||||||
|
length."
|
||||||
|
[clip sid]
|
||||||
|
(let [frames (clip/frames clip sid)]
|
||||||
|
(reduce
|
||||||
|
(fn [c [id n]]
|
||||||
|
(let [source (node/source n)]
|
||||||
|
(if (and (nil? (:parent n))
|
||||||
|
(symbol/lane? (clip/symbol c source)))
|
||||||
|
(-> c
|
||||||
|
(assoc-in [:symbols sid :nodes id :span] [0 frames])
|
||||||
|
(assoc-in [:symbols sid :nodes id :time :at] 0)
|
||||||
|
(assoc-in [:symbols source :frames] frames))
|
||||||
|
c)))
|
||||||
|
clip
|
||||||
|
(get-in clip [:symbols sid :nodes]))))
|
||||||
|
|
||||||
(defn finish
|
(defn finish
|
||||||
"Commit `nodes` as symbol `sid`'s, or refuse.
|
"Commit `nodes` as symbol `sid`'s, or refuse.
|
||||||
|
|
||||||
|
|
@ -94,9 +116,11 @@
|
||||||
(and (> needed (:frames sym)) (= :keep extent))
|
(and (> needed (:frames sym)) (= :keep extent))
|
||||||
{:refused (str "the edit needs " needed " frames; extend the shot to continue")
|
{:refused (str "the edit needs " needed " frames; extend the shot to continue")
|
||||||
:required-frames needed}
|
:required-frames needed}
|
||||||
:else {:clip (cond-> (assoc-in clip [:symbols sid :nodes] nodes)
|
:else {:clip (fit-lanes
|
||||||
(> needed (:frames sym))
|
(cond-> (assoc-in clip [:symbols sid :nodes] nodes)
|
||||||
(assoc-in [:symbols sid :frames] needed))
|
(> needed (:frames sym))
|
||||||
|
(assoc-in [:symbols sid :frames] needed))
|
||||||
|
sid)
|
||||||
:selection selection})))
|
:selection selection})))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,11 @@
|
||||||
loaded (project/load
|
loaded (project/load
|
||||||
cid #js {:leaves (.-leaves clip-json)
|
cid #js {:leaves (.-leaves clip-json)
|
||||||
:blocks blocks})
|
:blocks blocks})
|
||||||
built (:clip loaded)]
|
;; Project FPS and the root timeline are one clock. This
|
||||||
|
;; also normalizes documents saved by the earlier model,
|
||||||
|
;; where changing project FPS left the root on its old
|
||||||
|
;; editing grid.
|
||||||
|
built (clip/set-root-fps (:clip loaded) (:fps (:clip loaded)))]
|
||||||
(let [entry (merge (select-keys built [:fps :width :height])
|
(let [entry (merge (select-keys built [:fps :width :height])
|
||||||
{:label (str (or (.-name clip-json) cid) " (saved)")
|
{:label (str (or (.-name clip-json) cid) " (saved)")
|
||||||
:cid cid
|
:cid cid
|
||||||
|
|
@ -606,7 +610,7 @@
|
||||||
(if (or (not (#{:fps :width :height} key))
|
(if (or (not (#{:fps :width :height} key))
|
||||||
(not (and (integer? value) (pos? value))))
|
(not (and (integer? value) (pos? value))))
|
||||||
{}
|
{}
|
||||||
(let [db' (edit/edit db #(if (= key :fps) (clip/set-fps % value) (assoc % key value)))
|
(let [db' (edit/edit db #(if (= key :fps) (clip/set-root-fps % value) (assoc % key value)))
|
||||||
db' (assoc-in db' [:clip key] value)
|
db' (assoc-in db' [:clip key] value)
|
||||||
frame (min (dec (pb/frames db'))
|
frame (min (dec (pb/frames db'))
|
||||||
(js/Math.floor (* (get-in db [:playback :frame])
|
(js/Math.floor (* (get-in db [:playback :frame])
|
||||||
|
|
@ -636,7 +640,7 @@
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::symbol-setting
|
::symbol-setting
|
||||||
(fn [db [_ sid key value]]
|
(fn [db [_ sid key value]]
|
||||||
(if-not (and (#{:frames :width :height} key)
|
(if-not (and (#{:frames :fps :width :height} key)
|
||||||
(or (nil? value) (and (integer? value) (pos? value))))
|
(or (nil? value) (and (integer? value) (pos? value))))
|
||||||
db
|
db
|
||||||
(edit/edit db
|
(edit/edit db
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,12 @@
|
||||||
(let [{document :clip st :store} (store/entry (:clip/current db))
|
(let [{document :clip st :store} (store/entry (:clip/current db))
|
||||||
open (get-in db [:ui :open])
|
open (get-in db [:ui :open])
|
||||||
frame (editing-frame db document)
|
frame (editing-frame db document)
|
||||||
|
;; A lane is the open symbol's timeline, not an insert that happens to
|
||||||
|
;; begin where the playhead was when it was made. Giving that wrapper
|
||||||
|
;; the whole open-symbol window makes the row's promise true: it is a
|
||||||
|
;; destination at every frame. Ordinary symbols remain clips created at
|
||||||
|
;; the playhead.
|
||||||
|
frame (if lane? 0 frame)
|
||||||
into (if (= :top where)
|
into (if (= :top where)
|
||||||
(assoc (nest/inside document st open [] frame) :path [])
|
(assoc (nest/inside document st open [] frame) :path [])
|
||||||
(aimed-symbol document st db frame))
|
(aimed-symbol document st db frame))
|
||||||
|
|
@ -665,8 +671,10 @@
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::new-lane
|
::new-lane
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
;; A lane is an explicit top-level track of the open symbol. It must not
|
;; A lane is an explicit, persistent top-level track of the open symbol. It
|
||||||
;; become nested merely because the previously created lane is still aimed.
|
;; must not become nested merely because the previously created lane is
|
||||||
|
;; still aimed, and it spans the open symbol rather than starting at the
|
||||||
|
;; current playhead.
|
||||||
(create-container db :top true)))
|
(create-container db :top true)))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -537,6 +537,11 @@
|
||||||
[:div.inspector-form
|
[:div.inspector-form
|
||||||
[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)
|
||||||
|
#(rf/dispatch (if (= sid (clip-domain/opens-on clip))
|
||||||
|
[::project/project-setting :fps %]
|
||||||
|
[::project/symbol-setting sid :fps %]))
|
||||||
|
nil busy?]
|
||||||
[number-field "width" (:width sym)
|
[number-field "width" (:width sym)
|
||||||
#(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)
|
||||||
|
|
|
||||||
|
|
@ -137,12 +137,15 @@
|
||||||
(defn- select!
|
(defn- select!
|
||||||
"Select the node at row path `path` of the open symbol — the selection a
|
"Select the node at row path `path` of the open symbol — the selection a
|
||||||
timeline row makes, so the row, the inspector and the stage all show it — or
|
timeline row makes, so the row, the inspector and the stage all show it — or
|
||||||
nothing."
|
the open symbol itself when `path` is empty. Blank stage is an explicit place,
|
||||||
|
not merely an absence of a picked shape, so it clears a stale drawing target
|
||||||
|
as well as the inspector selection."
|
||||||
[{:keys [open f] :as ctx} path]
|
[{:keys [open f] :as ctx} path]
|
||||||
(let [{document :clip st :store} (loaded ctx)]
|
(let [{document :clip st :store} (loaded ctx)]
|
||||||
(rf/dispatch [::ui/select (when-let [{:keys [sid id]} (when (seq path)
|
(if-let [{:keys [sid id]} (when (seq path)
|
||||||
(nest/placement document st open path f))]
|
(nest/placement document st open path f))]
|
||||||
[:node sid id path])])))
|
(rf/dispatch [::ui/select [:node sid id path]])
|
||||||
|
(rf/dispatch [::ui/aim nil]))))
|
||||||
|
|
||||||
(defn- begin!
|
(defn- begin!
|
||||||
"Start dragging `kind` of the node at `path` from stage point `p`."
|
"Start dragging `kind` of the node at `path` from stage point `p`."
|
||||||
|
|
|
||||||
|
|
@ -1051,8 +1051,11 @@
|
||||||
visible (cond-> (vec picture)
|
visible (cond-> (vec picture)
|
||||||
(seq sounds) (-> (conj {:path [::sounds] :kind :section :label "audio"})
|
(seq sounds) (-> (conj {:path [::sounds] :kind :section :label "audio"})
|
||||||
(into sounds)))
|
(into sounds)))
|
||||||
;; Roughly ten labels, on a round number of frames.
|
;; Major marks are whole seconds in the OPEN symbol's clock. On long
|
||||||
step (* 10 (js/Math.ceil (/ frames 100)))
|
;; timelines use a whole-number multiple of a second to keep roughly
|
||||||
|
;; ten labels; never invent an FPS-blind 20/40/60 ruler.
|
||||||
|
fps (max 1 (or (clip/fps clip open) 1))
|
||||||
|
step (* fps (max 1 (js/Math.ceil (/ frames (* fps 10)))))
|
||||||
;; THE OTHER DIRECTION, AND THE ONLY PLACE THIS PANE GOES THERE. The
|
;; THE OTHER DIRECTION, AND THE ONLY PLACE THIS PANE GOES THERE. The
|
||||||
;; ruler is in the open symbol's frames and the transport counts output
|
;; ruler is in the open symbol's frames and the transport counts output
|
||||||
;; frames, so scrubbing names a mark and seeks to the output frame that
|
;; frames, so scrubbing names a mark and seeks to the output frame that
|
||||||
|
|
@ -1063,9 +1066,19 @@
|
||||||
[:section.pane.time
|
[:section.pane.time
|
||||||
[transport]
|
[transport]
|
||||||
[:div.tl-body
|
[:div.tl-body
|
||||||
|
;; Blank timeline space means the open symbol. Use the same `aim`
|
||||||
|
;; gesture as its breadcrumb so both the visible selection and the
|
||||||
|
;; drawing destination return to the root. Child controls stop their
|
||||||
|
;; own events; the target check keeps ordinary row/bar clicks local.
|
||||||
|
{:on-click (fn [^js e]
|
||||||
|
(when (= (.-target e) (.-currentTarget e))
|
||||||
|
(rf/dispatch [::ui/aim nil])))}
|
||||||
[:div.tl-labels
|
[:div.tl-labels
|
||||||
;; Empty label space takes a row back out to the top of the open symbol.
|
;; Empty label space takes a row back out to the top of the open symbol.
|
||||||
{:on-drag-over (fn [^js e]
|
{:on-click (fn [^js e]
|
||||||
|
(when (= (.-target e) (.-currentTarget e))
|
||||||
|
(rf/dispatch [::ui/aim nil])))
|
||||||
|
:on-drag-over (fn [^js e]
|
||||||
(when (drag/row)
|
(when (drag/row)
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
(set! (.. e -dataTransfer -dropEffect) "move")))
|
(set! (.. e -dataTransfer -dropEffect) "move")))
|
||||||
|
|
@ -1083,7 +1096,10 @@
|
||||||
[label-cell row selection target over solo tracing renaming draft])
|
[label-cell row selection target over solo tracing renaming draft])
|
||||||
{:key (str (:path row))})))]
|
{:key (str (:path row))})))]
|
||||||
[:div.tl-tracks
|
[:div.tl-tracks
|
||||||
{:on-drag-enter (fn [^js e] (when (drag/accepts?) (.preventDefault e)))
|
{:on-click (fn [^js e]
|
||||||
|
(when (= (.-target e) (.-currentTarget e))
|
||||||
|
(rf/dispatch [::ui/aim nil])))
|
||||||
|
:on-drag-enter (fn [^js e] (when (drag/accepts?) (.preventDefault e)))
|
||||||
:on-drag-over (fn [^js e]
|
:on-drag-over (fn [^js e]
|
||||||
(when (drag/accepts?)
|
(when (drag/accepts?)
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
|
|
@ -1105,10 +1121,10 @@
|
||||||
:on-drop (fn [^js e]
|
:on-drop (fn [^js e]
|
||||||
(.preventDefault e)
|
(.preventDefault e)
|
||||||
(drag/land! (frame-at e frames) nil))
|
(drag/land! (frame-at e frames) nil))
|
||||||
;; Five frames as a percentage of the whole span, handed to the
|
;; The rows and ruler share this exact major interval. Keeping a
|
||||||
;; stylesheet so the frame grid can be a repeating background instead
|
;; second, hard-coded five-frame grid here made its lines disagree
|
||||||
;; of a div per frame. A 900-frame take is 900 elements nobody needs.
|
;; with the numbered marks whenever the symbol's FPS changed.
|
||||||
:style {"--tick" (str (* 100 (/ 5 frames)) "%")}}
|
:style {"--tick" (str (* 100 (/ step frames)) "%")}}
|
||||||
[:div.tl-ruler
|
[:div.tl-ruler
|
||||||
{:on-pointer-down (fn [^js e]
|
{:on-pointer-down (fn [^js e]
|
||||||
(rf/dispatch [::pb/seek (seek-to e frames)])
|
(rf/dispatch [::pb/seek (seek-to e frames)])
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,36 @@
|
||||||
(is (= 3 (clip/first-output-frame doc :main 7)))
|
(is (= 3 (clip/first-output-frame doc :main 7)))
|
||||||
(is (= [0 60] (:span (first (timeline/rows doc :main #{})))))))
|
(is (= [0 60] (:span (first (timeline/rows doc :main #{})))))))
|
||||||
|
|
||||||
|
(deftest changing-fps-before-authoring-moves-the-empty-canvas-to-that-grid
|
||||||
|
(let [doc (clip/set-fps (clip/blank) 12)]
|
||||||
|
(is (= 12 (:fps doc)))
|
||||||
|
(is (nil? (get-in doc [:symbols :main :fps])))
|
||||||
|
(is (= 48 (clip/frames doc :main)))
|
||||||
|
(is (= 48 (clip/output-frames doc :main)))
|
||||||
|
(is (= 8 (clip/shown-frame doc :main 8)))
|
||||||
|
(is (= 8 (clip/first-output-frame doc :main 8)))))
|
||||||
|
|
||||||
|
(deftest changing-fps-after-authoring-preserves-the-symbols-native-grid
|
||||||
|
(let [started (assoc-in (clip/blank) [:symbols :main :nodes :mark]
|
||||||
|
{:id :mark :kind :rect :z "a"})
|
||||||
|
doc (clip/set-fps started 12)]
|
||||||
|
(is (= 30 (get-in doc [:symbols :main :fps])))
|
||||||
|
(is (= 120 (clip/frames doc :main)))
|
||||||
|
(is (= 48 (clip/output-frames doc :main)))))
|
||||||
|
|
||||||
|
(deftest project-fps-is-the-root-symbols-editing-grid
|
||||||
|
(let [doc (-> (clip/blank)
|
||||||
|
(assoc-in [:symbols :main :nodes :child]
|
||||||
|
{:id :child :kind :instance :z "a"
|
||||||
|
:source {:symbol :nested}})
|
||||||
|
(assoc-in [:symbols :nested]
|
||||||
|
{:id :nested :fps 30 :frames 90 :nodes {}})
|
||||||
|
(clip/set-root-fps 12))]
|
||||||
|
(is (= 12 (:fps doc)) "the output grid")
|
||||||
|
(is (= 12 (clip/fps doc :main)) "is also the root editing grid")
|
||||||
|
(is (= 30 (clip/fps doc :nested)) "while a nested symbol keeps its own grid")
|
||||||
|
(is (= 120 (clip/frames doc :main)) "frame positions are not rewritten")))
|
||||||
|
|
||||||
(deftest crossing-to-the-output-grid-and-back-lands-on-the-frame-it-names
|
(deftest crossing-to-the-output-grid-and-back-lands-on-the-frame-it-names
|
||||||
;; `first-output-frame` is the inverse of `shown-frame` as far as a floor has
|
;; `first-output-frame` is the inverse of `shown-frame` as far as a floor has
|
||||||
;; one: seeking to the output frame it names puts the playhead on a frame at or
|
;; one: seeking to the output frame it names puts the playhead on a frame at or
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
[arthur.domain.clip :as clip]
|
[arthur.domain.clip :as clip]
|
||||||
[arthur.domain.history :as history]
|
[arthur.domain.history :as history]
|
||||||
[arthur.domain.leaf :as leaf]
|
[arthur.domain.leaf :as leaf]
|
||||||
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.sequence-test :as fixture]
|
[arthur.domain.sequence-test :as fixture]
|
||||||
[arthur.domain.span :as span]
|
[arthur.domain.span :as span]
|
||||||
[arthur.events.ui :as ui]
|
[arthur.events.ui :as ui]
|
||||||
|
|
@ -16,20 +17,41 @@
|
||||||
(let [doc (clip/blank)
|
(let [doc (clip/blank)
|
||||||
id (store/install! {:clip doc :store {}} key)]
|
id (store/install! {:clip doc :store {}} key)]
|
||||||
(reset! rf-db/app-db {:clip/current id :paint/revision 0
|
(reset! rf-db/app-db {:clip/current id :paint/revision 0
|
||||||
:ui {:open :main} :playback {:frame 0}})
|
:ui {:open :main} :playback {:frame 6}})
|
||||||
(rf/dispatch-sync event)
|
(rf/dispatch-sync event)
|
||||||
(let [db @rf-db/app-db
|
(let [db @rf-db/app-db
|
||||||
saved (:clip (store/entry id))
|
saved (:clip (store/entry id))
|
||||||
[_ _ instance-id] (get-in db [:ui :selection])
|
[_ _ instance-id] (get-in db [:ui :selection])
|
||||||
sid (get-in saved [:symbols :main :nodes instance-id :source :symbol])]
|
sid (get-in saved [:symbols :main :nodes instance-id :source :symbol])]
|
||||||
{:db db :symbol (clip/symbol saved sid)})))]
|
{:db db :document saved :instance-id instance-id
|
||||||
|
:symbol (clip/symbol saved sid)})))]
|
||||||
(let [{ordinary :symbol} (run [::ui/new-symbol :inside] "explicit-symbol")
|
(let [{ordinary :symbol} (run [::ui/new-symbol :inside] "explicit-symbol")
|
||||||
{lane :symbol lane-db :db} (run [::ui/new-lane] "explicit-lane")]
|
{lane :symbol lane-db :db document :document instance-id :instance-id}
|
||||||
|
(run [::ui/new-lane] "explicit-lane")]
|
||||||
(is (nil? (:display ordinary)) "new symbol means ordinary symbol")
|
(is (nil? (:display ordinary)) "new symbol means ordinary symbol")
|
||||||
(is (= :lane (:display lane)) "only the lane command creates a lane")
|
(is (= :lane (:display lane)) "only the lane command creates a lane")
|
||||||
|
(is (= [0 (clip/frames document :main)]
|
||||||
|
(node/placed-span (get-in document [:symbols :main :nodes instance-id])))
|
||||||
|
"a lane exists across the open symbol, independent of the playhead")
|
||||||
|
(let [longer (assoc-in document [:symbols :main :frames] 300)
|
||||||
|
fitted (:clip (span/finish longer :main
|
||||||
|
(get-in longer [:symbols :main :nodes])
|
||||||
|
nil :keep))
|
||||||
|
lane-id (node/source (get-in fitted [:symbols :main :nodes instance-id]))]
|
||||||
|
(is (= [0 300]
|
||||||
|
(node/placed-span (get-in fitted [:symbols :main :nodes instance-id])))
|
||||||
|
"the lane follows a later change to its parent's extent")
|
||||||
|
(is (= 300 (clip/frames fitted lane-id))))
|
||||||
(is (some? (get-in lane-db [:ui :target]))
|
(is (some? (get-in lane-db [:ui :target]))
|
||||||
"the new lane is aimed so drawing and pool drops can go into it"))))
|
"the new lane is aimed so drawing and pool drops can go into it"))))
|
||||||
|
|
||||||
|
(deftest aiming-the-root-clears-selection-and-drawing-target
|
||||||
|
(let [db {:ui {:selection [:node :main :shape [:lane :shape]]
|
||||||
|
:target {:sid :main :id :lane :path [:lane]}}}
|
||||||
|
after (ui/aimed 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 #{})
|
||||||
|
|
|
||||||
|
|
@ -1026,10 +1026,9 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
||||||
min-width: max(340px, calc((100% - var(--label)) * var(--tl-zoom, 1)));
|
min-width: max(340px, calc((100% - var(--label)) * var(--tl-zoom, 1)));
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
/* The frame grid, five frames to a division, as a background rather than as
|
/* The major frame grid, using the same whole-second interval as the numbered
|
||||||
an element per frame: a 900-frame take is 900 divs nobody needs in the DOM.
|
ruler. It is a background rather than an element per mark; `--tick` is set
|
||||||
`--tick` is five frames as a percentage of the span, set from the component
|
by the component because only it knows the open symbol's length and FPS. */
|
||||||
because only it knows how long the clip is. */
|
|
||||||
background-image:
|
background-image:
|
||||||
repeating-linear-gradient(90deg,
|
repeating-linear-gradient(90deg,
|
||||||
var(--grid-5) 0 1px, transparent 1px var(--tick, 10%));
|
var(--grid-5) 0 1px, transparent 1px var(--tick, 10%));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue