fixed creating in
This commit is contained in:
parent
2697401aad
commit
a90e0cfb61
5 changed files with 99 additions and 17 deletions
|
|
@ -72,6 +72,12 @@ Once resolved, every creation command follows the destination kind.
|
||||||
- Selecting an existing cel changes the destination from the lane to the symbol
|
- Selecting an existing cel changes the destination from the lane to the symbol
|
||||||
placed by that cel; subsequent symbols and shapes become children there.
|
placed by that cel; subsequent symbols and shapes become children there.
|
||||||
|
|
||||||
|
Double-clicking a lane creates an empty cel at the playhead; beginning a drawing
|
||||||
|
creates a drawing cel there. These are the same lane-creation operation with
|
||||||
|
different payloads. The pointer chooses the lane, never a second creation time.
|
||||||
|
The resulting cel is selected, so it immediately becomes the preferred target:
|
||||||
|
drawing again enters that cel's symbol instead of replacing it.
|
||||||
|
|
||||||
Thus no separate "new cel" versus "add inside" mode is needed. Selecting the
|
Thus no separate "new cel" versus "add inside" mode is needed. Selecting the
|
||||||
lane header says new cel; selecting a cel says add inside.
|
lane header says new cel; selecting a cel says add inside.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,39 @@
|
||||||
is actually available. See `docs/creating-in.md`."
|
is actually available. See `docs/creating-in.md`."
|
||||||
(:require [arthur.domain.clip :as clip]
|
(:require [arthur.domain.clip :as clip]
|
||||||
[arthur.domain.nest :as nest]
|
[arthur.domain.nest :as nest]
|
||||||
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.symbol :as symbol]))
|
[arthur.domain.symbol :as symbol]))
|
||||||
|
|
||||||
|
(defn- path-node
|
||||||
|
"The node at the end of an occurrence `path`, walked from `open`.
|
||||||
|
|
||||||
|
Selection also carries an owner sid and node id for commands that edit the
|
||||||
|
node directly. Those are deliberately not used here: the path is the address
|
||||||
|
of the row as seen from the open symbol, and is the only part that describes
|
||||||
|
every enclosing occurrence at arbitrary depth."
|
||||||
|
[document open path]
|
||||||
|
(loop [sid open [id & more] (seq path) found nil]
|
||||||
|
(if-not id
|
||||||
|
found
|
||||||
|
(when-let [n (get-in document [:symbols sid :nodes id])]
|
||||||
|
(if (seq more)
|
||||||
|
(when-let [inner (and (= :instance (:kind n)) (node/source n))]
|
||||||
|
(recur inner more n))
|
||||||
|
n)))))
|
||||||
|
|
||||||
(defn preferred-path
|
(defn preferred-path
|
||||||
"The container path structurally implied by `selection`.
|
"The container path structurally implied by `selection`.
|
||||||
|
|
||||||
Selecting an instance means inside it. Selecting any other node means its
|
Selecting an instance means inside it. Selecting any other node means its
|
||||||
containing symbol. An empty or non-node selection means the open symbol."
|
containing symbol. An empty or non-node selection means the open symbol."
|
||||||
[document selection]
|
[document open selection]
|
||||||
(let [[kind sid id selected-path] selection
|
(let [[kind _sid id selected-path] selection
|
||||||
path (when (= :node kind)
|
path (when (= :node kind)
|
||||||
(vec (or (seq selected-path) (when id [id]))))]
|
(vec (or (seq selected-path) (when id [id]))))
|
||||||
|
selected (path-node document open path)]
|
||||||
(cond
|
(cond
|
||||||
(empty? path) []
|
(empty? path) []
|
||||||
(= :instance (get-in document [:symbols sid :nodes id :kind])) path
|
(= :instance (:kind selected)) path
|
||||||
:else (vec (butlast path)))))
|
:else (vec (butlast path)))))
|
||||||
|
|
||||||
(defn target
|
(defn target
|
||||||
|
|
@ -33,7 +52,7 @@
|
||||||
|
|
||||||
Returns `{:kind :lane|:symbol :sid :path :frame :matrix :time}`."
|
Returns `{:kind :lane|:symbol :sid :path :frame :matrix :time}`."
|
||||||
[document store open selection frame]
|
[document store open selection frame]
|
||||||
(let [preferred (preferred-path document selection)]
|
(let [preferred (preferred-path document open selection)]
|
||||||
(some (fn [path]
|
(some (fn [path]
|
||||||
(when-let [inside (nest/inside document store open path frame)]
|
(when-let [inside (nest/inside document store open path frame)]
|
||||||
(when-let [sid (:sid inside)]
|
(when-let [sid (:sid inside)]
|
||||||
|
|
|
||||||
|
|
@ -345,9 +345,16 @@
|
||||||
|
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::toggle-row
|
::set-row-expanded
|
||||||
(fn [db [_ path]]
|
(fn [db [_ path expanded?]]
|
||||||
(update-in db [:ui :expanded] #(if (contains? % path) (disj % path) (conj % path)))))
|
;; The disclosure control says what the next state is instead of asking us
|
||||||
|
;; to invert whatever happens to be in app-db by the time its event runs.
|
||||||
|
;; A structural drop may reveal this path between pointer-down and click;
|
||||||
|
;; blindly toggling then immediately closed the row whose triangle was still
|
||||||
|
;; painted as closed.
|
||||||
|
(update-in db [:ui :expanded]
|
||||||
|
(fn [paths]
|
||||||
|
((if expanded? conj disj) (set paths) path)))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::solo
|
::solo
|
||||||
|
|
@ -800,8 +807,11 @@
|
||||||
;; One gesture and one creation path for every lane. The destination decides
|
;; One gesture and one creation path for every lane. The destination decides
|
||||||
;; the kind: an ordinary lane gets a blank symbol; the synthetic palette row
|
;; the kind: an ordinary lane gets a blank symbol; the synthetic palette row
|
||||||
;; gets a blank palette symbol whose placement starts by inheriting.
|
;; gets a blank palette symbol whose placement starts by inheriting.
|
||||||
(fn [db [_ frame target]]
|
(fn [db [_ _pointer-frame target]]
|
||||||
(let [{document :clip st :store} (store/entry (:clip/current db))
|
(let [{document :clip st :store} (store/entry (:clip/current db))
|
||||||
|
;; Creation always happens at the playhead. The double-click only
|
||||||
|
;; names the lane; it is not a second, pointer-based time cursor.
|
||||||
|
frame (editing-frame db document)
|
||||||
palette? (= :arthur.ui.timeline/palette-track (first target))
|
palette? (= :arthur.ui.timeline/palette-track (first target))
|
||||||
root-sid (second target)
|
root-sid (second target)
|
||||||
root (when palette? (clip/symbol document root-sid))
|
root (when palette? (clip/symbol document root-sid))
|
||||||
|
|
|
||||||
|
|
@ -656,11 +656,14 @@
|
||||||
:else [::ui/group [from path]])))))}))
|
:else [::ui/group [from path]])))))}))
|
||||||
[:button.tl-twist
|
[:button.tl-twist
|
||||||
{:disabled (not expandable?)
|
{:disabled (not expandable?)
|
||||||
|
;; This button lives inside a draggable row label. Do not let a tiny
|
||||||
|
;; pointer movement turn disclosure into a native row drag (whose click
|
||||||
|
;; is then suppressed by the browser).
|
||||||
|
:draggable false
|
||||||
|
:on-pointer-down (fn [^js e] (.stopPropagation e))
|
||||||
:on-click (fn [^js e]
|
:on-click (fn [^js e]
|
||||||
(println "heyyy")
|
|
||||||
(.stopPropagation e)
|
(.stopPropagation e)
|
||||||
(rf/dispatch [::ui/toggle-row path]))
|
(rf/dispatch [::ui/set-row-expanded path (not expanded?)]))}
|
||||||
:class (println expandable?)}
|
|
||||||
(when expandable? (if expanded? "▾" "▸"))]
|
(when expandable? (if expanded? "▾" "▸"))]
|
||||||
;; A LANE IS NOT AN INSTANCE WEARING A DIFFERENT HAT, to read. Its one row
|
;; A LANE IS NOT AN INSTANCE WEARING A DIFFERENT HAT, to read. Its one row
|
||||||
;; holds blocks that follow one another in time, where every other row
|
;; holds blocks that follow one another in time, where every other row
|
||||||
|
|
|
||||||
|
|
@ -72,26 +72,30 @@
|
||||||
(vals (get-in saved [:symbols :main :nodes]))))
|
(vals (get-in saved [:symbols :main :nodes]))))
|
||||||
"claiming the frame leaves no overlapping cel"))))
|
"claiming the frame leaves no overlapping cel"))))
|
||||||
|
|
||||||
(deftest double-click-creation-uses-the-lane-and-pointer-frame
|
(deftest double-click-creation-uses-the-lane-and-playhead
|
||||||
(let [doc (fixture/document)
|
(let [doc (fixture/document)
|
||||||
id (store/install! {:clip doc :store {}} "double-click-new-symbol")]
|
id (store/install! {:clip doc :store {}} "double-click-new-symbol")]
|
||||||
(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 5}})
|
||||||
(rf/dispatch-sync [::ui/new-symbol-at 5 [:node :main nil []]])
|
(rf/dispatch-sync [::ui/new-symbol-at 5 [:node :main nil []]])
|
||||||
(let [saved (:clip (store/entry id))
|
(let [saved (:clip (store/entry id))
|
||||||
[_ sid instance-id] (get-in @rf-db/app-db [:ui :selection])
|
[_ sid instance-id] (get-in @rf-db/app-db [:ui :selection])
|
||||||
|
selection (get-in @rf-db/app-db [:ui :selection])
|
||||||
instance (get-in saved [:symbols sid :nodes instance-id])]
|
instance (get-in saved [:symbols sid :nodes instance-id])]
|
||||||
(is (= :main sid))
|
(is (= :main sid))
|
||||||
(is (= [5 6] (node/placed-span instance)))
|
(is (= [5 6] (node/placed-span instance)))
|
||||||
(is (= 1 (clip/frames saved (node/source instance))))
|
(is (= 1 (clip/frames saved (node/source instance))))
|
||||||
(is (zero? (get-in @rf-db/app-db [:playback :frame]))
|
(is (= (node/source instance)
|
||||||
"the pointer frame, not the playhead, chooses the new cel's time"))))
|
(:sid (creation/target saved {} :main selection 5)))
|
||||||
|
"the new cel can immediately be selected as the creation target")
|
||||||
|
(is (= 5 (get-in @rf-db/app-db [:playback :frame]))
|
||||||
|
"the playhead chooses the new cel's time"))))
|
||||||
|
|
||||||
(deftest the-same-double-click-command-creates-a-palette-symbol-on-the-palette-row
|
(deftest the-same-double-click-command-creates-a-palette-symbol-on-the-palette-row
|
||||||
(let [doc (clip/blank)
|
(let [doc (clip/blank)
|
||||||
id (store/install! {:clip doc :store {}} "double-click-palette-symbol")]
|
id (store/install! {:clip doc :store {}} "double-click-palette-symbol")]
|
||||||
(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 5}})
|
||||||
(rf/dispatch-sync [::ui/new-symbol-at 5
|
(rf/dispatch-sync [::ui/new-symbol-at 5
|
||||||
[:arthur.ui.timeline/palette-track :main]])
|
[:arthur.ui.timeline/palette-track :main]])
|
||||||
(let [saved (:clip (store/entry id))
|
(let [saved (:clip (store/entry id))
|
||||||
|
|
@ -213,6 +217,36 @@
|
||||||
(at 25))
|
(at 25))
|
||||||
"a lane behind an inactive parent occurrence is not valid")))
|
"a lane behind an inactive parent occurrence is not valid")))
|
||||||
|
|
||||||
|
(deftest creation-uses-the-occurrence-path-at-arbitrary-depth
|
||||||
|
(let [instance (fn [id source span]
|
||||||
|
{:id id :kind :instance :z "a" :span span
|
||||||
|
:time {:mode :map :at 0 :rate 1}
|
||||||
|
:source {:symbol source}
|
||||||
|
:playback {:in 0 :speed 1 :end :stop}})
|
||||||
|
doc {:fps 24 :width 20 :height 20
|
||||||
|
:symbols
|
||||||
|
{:main {:id :main :frames 20
|
||||||
|
:nodes {:outer (instance :outer :ordinary [0 20])}}
|
||||||
|
:ordinary {:id :ordinary :frames 20
|
||||||
|
:nodes {:lane (instance :lane :symbol-7 [0 20])}}
|
||||||
|
:symbol-7 {:id :symbol-7 :name "symbol-7" :display :lane :frames 20
|
||||||
|
:nodes {:cel (instance :cel :symbol-8 [4 10])}}
|
||||||
|
:symbol-8 {:id :symbol-8 :name "symbol-8" :frames 6 :nodes {}}}}
|
||||||
|
lane-selection [:node :ordinary :lane [:outer :lane]]
|
||||||
|
;; The path, rather than these redundant owner fields, is authoritative
|
||||||
|
;; for creation. A row address may name the occurrence from an outer
|
||||||
|
;; view; resolution must still enter the terminal cel.
|
||||||
|
cel-selection [:node :ordinary :lane [:outer :lane :cel]]
|
||||||
|
pick #(select-keys (creation/target doc nil :main % 5)
|
||||||
|
[:kind :sid :path :frame])]
|
||||||
|
(is (= {:kind :lane :sid :symbol-7 :path [:outer :lane] :frame 5}
|
||||||
|
(pick lane-selection))
|
||||||
|
"the nested lane header remains the lane insertion surface")
|
||||||
|
(is (= {:kind :symbol :sid :symbol-8
|
||||||
|
:path [:outer :lane :cel] :frame 5}
|
||||||
|
(pick cel-selection))
|
||||||
|
"the cel enters its symbol no matter how deeply the lane is nested")))
|
||||||
|
|
||||||
(deftest creation-walks-out-of-an-instance-past-its-window
|
(deftest creation-walks-out-of-an-instance-past-its-window
|
||||||
(let [doc {:fps 24 :width 20 :height 20
|
(let [doc {:fps 24 :width 20 :height 20
|
||||||
:symbols
|
:symbols
|
||||||
|
|
@ -360,6 +394,16 @@
|
||||||
(is (= #{[:already-open]} (get-in after [:ui :expanded]))
|
(is (= #{[:already-open]} (get-in after [:ui :expanded]))
|
||||||
"disclosure is changed only by the twist control")))
|
"disclosure is changed only by the twist control")))
|
||||||
|
|
||||||
|
(deftest disclosure-sets-the-state-painted-by-the-button
|
||||||
|
;; A move can reveal a destination after the old, closed button has already
|
||||||
|
;; received pointer-down. Its eventual click must keep the row open instead
|
||||||
|
;; of toggling the newer state closed again.
|
||||||
|
(reset! rf-db/app-db {:ui {:expanded #{[:destination]}}})
|
||||||
|
(rf/dispatch-sync [::ui/set-row-expanded [:destination] true])
|
||||||
|
(is (= #{[:destination]} (get-in @rf-db/app-db [:ui :expanded])))
|
||||||
|
(rf/dispatch-sync [::ui/set-row-expanded [:destination] false])
|
||||||
|
(is (empty? (get-in @rf-db/app-db [:ui :expanded]))))
|
||||||
|
|
||||||
(deftest finishing-a-polygon-opens-no-rows
|
(deftest finishing-a-polygon-opens-no-rows
|
||||||
;; Expansion is the twist triangle's business. Finishing a shape used to open
|
;; Expansion is the twist triangle's business. Finishing a shape used to open
|
||||||
;; every row down to it, which inside a lane meant tearing its one row into a
|
;; every row down to it, which inside a lane meant tearing its one row into a
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue