Unify timeline placement and gesture geometry
This commit is contained in:
parent
4e5e02c856
commit
0cb9d0ebf6
16 changed files with 702 additions and 121 deletions
|
|
@ -36,8 +36,28 @@
|
|||
(is (= footage (clip/set-fps doc 30)))
|
||||
(is (= doc (leaf/clip "test" (leaf/leaves "test" doc))))
|
||||
(is (= [1 3 6 8 11 13] (mapv #(:size (first (draw %))) (range 6))))
|
||||
(is (= 7 (:frame (nest/inside doc store :main [:mark] 3))))
|
||||
(is (= [0 24] (:span (first (timeline/rows doc :main #{})))))))
|
||||
;; `nest/inside` and the timeline's rows are in the SYMBOL's own frames --
|
||||
;; see `docs/one-grid-plan.md`. Frame 7 of :main is frame 7 of :main; the
|
||||
;; output frame that shows it is 3, and `shown-frame` is the one function
|
||||
;; that crosses between the two.
|
||||
(is (= 7 (:frame (nest/inside doc store :main [:mark] 7))))
|
||||
(is (= 7 (clip/shown-frame doc :main 3)))
|
||||
(is (= 3 (clip/first-output-frame doc :main 7)))
|
||||
(is (= [0 60] (:span (first (timeline/rows doc :main #{})))))))
|
||||
|
||||
(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
|
||||
;; one: seeking to the output frame it names puts the playhead on a frame at or
|
||||
;; after the mark, never before it, and exactly on it whenever an output frame
|
||||
;; shows it at all.
|
||||
(doseq [project [8 12 24 30 60] native [12 24 30 60]]
|
||||
(let [doc (-> footage (assoc :fps project) (assoc-in [:symbols :main :fps] native))]
|
||||
(doseq [n (range 40)]
|
||||
(let [f (clip/first-output-frame doc :main n)]
|
||||
(is (integer? f))
|
||||
(is (>= (clip/shown-frame doc :main f) n)
|
||||
(str n " at " project "/" native))
|
||||
(is (or (zero? f) (< (clip/shown-frame doc :main (dec f)) n))))))))
|
||||
|
||||
(deftest imported-footage-uses-selection-for-picture-and-real-speed-for-audio
|
||||
(let [{doc :clip sid :sid} (bring/take (clip/set-fps (clip/blank) 12)
|
||||
|
|
|
|||
|
|
@ -281,3 +281,78 @@
|
|||
(get-in [:symbols :outer :nodes u]))]
|
||||
(is (empty? (node/problems v)))
|
||||
(is (= [0 36] (node/placed-span v)) "90 frames at 30 are 36 at 12")))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; the ruler IS the symbol's frames
|
||||
|
||||
(defn- on-twos
|
||||
"A 12fps project holding a 30fps lane, which is what changing project fps to
|
||||
12 leaves behind: `docs/time.md` records each symbol's own rate so its timing
|
||||
stays put. Two one-frame held clips in it, at 5 and at 19."
|
||||
[]
|
||||
(let [held (fn [id at] {:id id :kind :instance :z (str "a-" id) :span [0 1]
|
||||
;; As `span/held` writes it: no `:mode`, because
|
||||
;; `node/time-of` reads an absent one as `:map`.
|
||||
:time {:at at :rate 1}
|
||||
:source {:symbol :draw} :playback {:in 0 :speed 0 :end :stop}})]
|
||||
(-> (clip/blank)
|
||||
(assoc :fps 12)
|
||||
(assoc-in [:symbols :draw] {:id :draw :fps 30 :frames 1 :nodes {}})
|
||||
(assoc-in [:symbols :lane] {:id :lane :fps 30 :frames 60 :display :lane
|
||||
:nodes {:a (held :a 5) :b (held :b 19)}}))))
|
||||
|
||||
(deftest a-drag-in-the-open-symbol-is-frame-for-frame
|
||||
;; `df` counts the OPEN SYMBOL's frames, because that is what the ruler is
|
||||
;; drawn in -- see `docs/one-grid-plan.md`. So one frame of the gesture is one
|
||||
;; frame of the document, the project's output rate is not in the arithmetic at
|
||||
;; all, and nothing can land between two frames.
|
||||
(doseq [project [8 12 24 30 60]]
|
||||
(let [c (assoc (on-twos) :fps project)
|
||||
at #(get-in % [:symbols :lane :nodes :b :time :at])
|
||||
span #(node/placed-span (get-in % [:symbols :lane :nodes :b]))]
|
||||
(is (= 20 (at (:clip (nest/slide c :lane [:b] 1)))) (str "at " project "fps"))
|
||||
(is (= 18 (at (:clip (nest/slide c :lane [:b] -1)))))
|
||||
(is (= 24 (at (:clip (nest/slide c :lane [:b] 5)))))
|
||||
(is (= [20 21] (span (:clip (nest/slide c :lane [:b] 1)))))
|
||||
(testing "an edge drag too"
|
||||
(let [grown (nest/resize-out c :lane [:b] 1 false)]
|
||||
(is (nil? (:refused grown)) (:refused grown))
|
||||
(is (= [19 21] (node/placed-span (get-in (:clip grown) [:symbols :lane :nodes :b]))))
|
||||
(is (= [18 20] (node/placed-span
|
||||
(get-in (:clip (nest/resize-in c :lane [:b] -1))
|
||||
[:symbols :lane :nodes :b])))))))))
|
||||
|
||||
(deftest a-drag-through-a-retimed-instance-still-lands-on-a-whole-frame
|
||||
;; The one case where a ruler frame is not a frame of the space being edited:
|
||||
;; a row reached THROUGH an instance somebody deliberately retimed. At double
|
||||
;; speed one frame of the ruler is two inside, and at half speed it is half of
|
||||
;; one -- which has to round, because a frame number cannot fall between two
|
||||
;; frames. That rounding is `nest/dragged` and it is the whole of its job.
|
||||
(let [inner (fn [rate]
|
||||
(-> (on-twos)
|
||||
(assoc-in [:symbols :outer]
|
||||
{:id :outer :fps 30 :frames 60
|
||||
:nodes {:ins {:id :ins :kind :instance :z "a" :span [0 60]
|
||||
:time {:mode :map :at 0 :rate rate}
|
||||
:source {:symbol :lane}
|
||||
:playback {:in 0 :speed 1 :end :stop}}}})))
|
||||
at (fn [rate df]
|
||||
(get-in (:clip (nest/slide (inner rate) :outer [:ins :b] df))
|
||||
[:symbols :lane :nodes :b :time :at]))]
|
||||
(is (= 21 (at 2 1)) "double speed inside: one frame of the ruler is two")
|
||||
(is (= 23 (at 2 2)))
|
||||
(is (= 20 (at 0.5 2)) "half speed: two frames of the ruler are one inside")
|
||||
(is (= 20 (at 0.5 1)) "and one lands on the nearer whole frame, half upwards")
|
||||
(is (every? integer? (map #(at 0.5 %) (range -4 5))))))
|
||||
|
||||
(deftest sliding-a-held-clip-moves-it-rather-than-forgetting-where-it-was
|
||||
;; `span/held` writes `{:at f :rate 1}` with no `:mode`, and the slide used to
|
||||
;; read that as "no time map" and replace the whole thing — so the first drag
|
||||
;; of a freshly drawn clip threw its `:at` away and jumped it to the head of
|
||||
;; the lane. Same grid both sides here, so the only question is the mode.
|
||||
(let [c (assoc (on-twos) :fps 30)
|
||||
slid (:clip (nest/slide c :lane [:b] 3))]
|
||||
(is (= 22 (get-in slid [:symbols :lane :nodes :b :time :at])))
|
||||
(is (= [22 23] (node/placed-span (get-in slid [:symbols :lane :nodes :b]))))
|
||||
(is (= 1 (:rate (get-in slid [:symbols :lane :nodes :b :time])))
|
||||
"and the rest of its map is still there")))
|
||||
|
|
|
|||
|
|
@ -115,3 +115,73 @@
|
|||
lane (first (filter :lane? (timeline/rows doc :main #{} nil)))]
|
||||
(is (= [:vo] (mapv :id (:cels lane))))
|
||||
(is (empty? (timeline/sound-rows doc :main #{})))))
|
||||
|
||||
(deftest aiming-the-open-lanes-own-row-is-a-selection-and-not-a-crash
|
||||
;; `rows` names the open symbol's own lane row `[:node sid nil []]`, and
|
||||
;; `selected` used to open the rows above a selection with `(pop path)` —
|
||||
;; which THROWS on `[]`, aborting 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.
|
||||
(let [doc (fixture/document)
|
||||
id (store/install! {:clip doc :store {}} "aim-the-open-lane")
|
||||
db {:clip/current id :paint/revision 0
|
||||
:ui {:open :main :target {:sid :main :id :a :path [:a]}}
|
||||
:playback {:frame 0}}
|
||||
lane (first (filter :lane? (timeline/rows doc :main #{})))
|
||||
after (ui/aimed db (:select lane))]
|
||||
(is (= [:node :main nil []] (:select lane)))
|
||||
(is (= [:node :main nil []] (get-in after [:ui :selection])))
|
||||
(is (nil? (get-in after [:ui :target]))
|
||||
"the open symbol IS the place, so aiming its own row clears the target")
|
||||
(is (= [] (ui/where-new-goes doc after)))
|
||||
(is (empty? (get-in after [:ui :expanded]))
|
||||
"and there are no rows above the top to open")))
|
||||
|
||||
(deftest finishing-a-polygon-opens-no-rows
|
||||
;; 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
|
||||
;; portal, that clip's channels and every shape already in the drawing.
|
||||
(let [doc (fixture/document)
|
||||
id (store/install! {:clip doc :store {}} "finish-opens-nothing")
|
||||
db {:clip/current id :paint/revision 0
|
||||
:ui {:open :main :tool :polygon
|
||||
:target {:sid :main :id :a :path [:a]}
|
||||
:draft [10 10 40 10 40 40]}
|
||||
:playback {:frame 1}}]
|
||||
(reset! rf-db/app-db db)
|
||||
(rf/dispatch-sync [::ui/finish-polygon])
|
||||
(let [after @rf-db/app-db
|
||||
[kind sid shape-id path] (get-in after [:ui :selection])]
|
||||
(is (= :node kind))
|
||||
(is (= :drawing-a sid) "the shape went into the drawing the aimed clip places")
|
||||
(is (= [:a shape-id] path))
|
||||
(is (empty? (get-in after [:ui :expanded])))
|
||||
(is (nil? (get-in after [:ui :tool]))))))
|
||||
|
||||
(deftest a-clip-made-for-a-drawing-is-selected-in-the-symbol-it-lives-in
|
||||
;; The clip `beginning-polygon` materializes lives in the symbol the LANE
|
||||
;; draws. Naming the open one instead left a selection that looked up to
|
||||
;; nothing, so the inspector, the breadcrumb and every span command went blank
|
||||
;; on a drawing that had just been created.
|
||||
(let [doc (-> (fixture/document)
|
||||
;; The last of the lane's three clips taken out, so frames 8
|
||||
;; to 12 are a gap and drawing there makes the held clip that
|
||||
;; was missing rather than landing in one that is there.
|
||||
(update-in [:symbols :main :nodes] dissoc :insert)
|
||||
(assoc-in [:symbols :shot]
|
||||
{:id :shot :fps 24 :frames 12
|
||||
:nodes {:girl {:id :girl :kind :instance :z "b" :span [0 12]
|
||||
:time {:mode :map :at 0 :rate 1}
|
||||
:source {:symbol :main}
|
||||
:playback {:in 0 :speed 1 :end :stop}}}}))
|
||||
id (store/install! {:clip doc :store {}} "clip-selected-where-it-lives")
|
||||
db {:clip/current id :paint/revision 0
|
||||
:ui {:open :shot :target {:sid :shot :id :girl :path [:girl]}}
|
||||
:playback {:frame 9}}
|
||||
after (ui/beginning-polygon db)
|
||||
[_ sid clip-id path] (get-in after [:ui :selection])
|
||||
saved (:clip (store/entry id))]
|
||||
(is (= :main sid) "the lane's symbol, not the open one")
|
||||
(is (= [:girl clip-id] path))
|
||||
(is (some? (get-in saved [:symbols :main :nodes clip-id]))
|
||||
"and that is where the node actually is")))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue