Set the project's stage and rate, and give a symbol its own stage
The inspector edits the project's width, height and fps, and a symbol's length and, optionally, its own width and height. A symbol without them follows the project's (`clip/stage`); the stage, export and placement all read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1ac21fdcab
commit
0f9ce826ef
7 changed files with 107 additions and 22 deletions
|
|
@ -68,6 +68,14 @@
|
||||||
[clip sid]
|
[clip sid]
|
||||||
(:frames (symbol clip sid)))
|
(:frames (symbol clip sid)))
|
||||||
|
|
||||||
|
(defn stage
|
||||||
|
"A symbol's stage as `[width height]`: its own, or the clip's where it has none.
|
||||||
|
Absent rather than copied in at creation, so a symbol nobody has sized follows
|
||||||
|
the project's size when that changes."
|
||||||
|
[clip sid]
|
||||||
|
(let [sym (symbol clip sid)]
|
||||||
|
[(or (:width sym) (:width clip)) (or (:height sym) (:height clip))]))
|
||||||
|
|
||||||
(defn update-symbol
|
(defn update-symbol
|
||||||
"Apply f to one symbol in place."
|
"Apply f to one symbol in place."
|
||||||
[clip sid f & args]
|
[clip sid f & args]
|
||||||
|
|
@ -223,7 +231,7 @@
|
||||||
(mapcat resolve (range (frames clip sid))))]
|
(mapcat resolve (range (frames clip sid))))]
|
||||||
(if x0
|
(if x0
|
||||||
[(/ (+ x0 x1) 2) (/ (+ y0 y1) 2)]
|
[(/ (+ x0 x1) 2) (/ (+ y0 y1) 2)]
|
||||||
[(/ (:width clip) 2) (/ (:height clip) 2)])))
|
(mapv #(/ % 2) (stage clip sid)))))
|
||||||
|
|
||||||
(defn place-symbol
|
(defn place-symbol
|
||||||
"An instance of symbol `sid`, inside symbol `host`, at `frame` of `host`.
|
"An instance of symbol `sid`, inside symbol `host`, at `frame` of `host`.
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@
|
||||||
;; 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 :palette])})
|
(select-keys sym [:name :frames :width :height :palette])})
|
||||||
(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))
|
||||||
|
|
|
||||||
|
|
@ -542,8 +542,11 @@
|
||||||
write.
|
write.
|
||||||
|
|
||||||
`:name` is what a person calls it, and is not its id: an id is what instances
|
`:name` is what a person calls it, and is not its id: an id is what instances
|
||||||
and saved leaves point at, so renaming a symbol must not change it."
|
and saved leaves point at, so renaming a symbol must not change it.
|
||||||
#{:id :name :frames :nodes :palette})
|
|
||||||
|
`:width` and `:height` are the symbol's own stage, and are absent until someone
|
||||||
|
sets them: a symbol without them uses the clip's — see `clip/stage`."
|
||||||
|
#{:id :name :frames :width :height :nodes :palette})
|
||||||
|
|
||||||
(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.
|
||||||
|
|
@ -593,6 +596,10 @@
|
||||||
(into (when-not (or (nil? (:frames sym)) (and (integer? (:frames sym)) (pos? (:frames sym))))
|
(into (when-not (or (nil? (:frames sym)) (and (integer? (:frames sym)) (pos? (:frames sym))))
|
||||||
[(str ":frames is " (pr-str (:frames sym))
|
[(str ":frames is " (pr-str (:frames sym))
|
||||||
" — a symbol is a frame SPACE, so its length is a positive integer")]))
|
" — a symbol is a frame SPACE, so its length is a positive integer")]))
|
||||||
|
(into (for [k [:width :height]
|
||||||
|
:let [v (get sym k)]
|
||||||
|
:when (and (some? v) (not (and (integer? v) (pos? v))))]
|
||||||
|
(str k " is " (pr-str v) " — a symbol stage dimension must be a positive integer")))
|
||||||
(into (try
|
(into (try
|
||||||
(doall (map #(depth nodes %) (keys nodes)))
|
(doall (map #(depth nodes %) (keys nodes)))
|
||||||
nil
|
nil
|
||||||
|
|
|
||||||
|
|
@ -580,6 +580,31 @@
|
||||||
:label (or (:label clip) (name id))
|
:label (or (:label clip) (name id))
|
||||||
:clip clip}}))))
|
:clip clip}}))))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
::project-setting
|
||||||
|
(fn [{:keys [db]} [_ key value]]
|
||||||
|
(if (or (not (#{:fps :width :height} key))
|
||||||
|
(not (and (integer? value) (pos? value))))
|
||||||
|
{}
|
||||||
|
(let [db' (-> (edit/edit db #(assoc % key value))
|
||||||
|
(assoc-in [:clip key] value)
|
||||||
|
(cond-> (= key :fps) (assoc-in [:clip :display-fps] value)))
|
||||||
|
frame (get-in db [:playback :frame])]
|
||||||
|
(cond-> {:db db'}
|
||||||
|
(= key :fps) (assoc ::pb/seek! [value (pb/frames db') frame]))))))
|
||||||
|
|
||||||
|
(rf/reg-event-db
|
||||||
|
::symbol-setting
|
||||||
|
(fn [db [_ sid key value]]
|
||||||
|
(if-not (and (#{:frames :width :height} key)
|
||||||
|
(or (nil? value) (and (integer? value) (pos? value))))
|
||||||
|
db
|
||||||
|
(edit/edit db
|
||||||
|
(fn [c]
|
||||||
|
(if (nil? value)
|
||||||
|
(update-in c [:symbols sid] dissoc key)
|
||||||
|
(assoc-in c [:symbols sid key] value)))))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::set-channel
|
::set-channel
|
||||||
;; `frame` is the node's own, as for a drawing key.
|
;; `frame` is the node's own, as for a drawing key.
|
||||||
|
|
|
||||||
|
|
@ -160,13 +160,14 @@
|
||||||
commit to, and so the arithmetic is assertable without a sink."
|
commit to, and so the arithmetic is assertable without a sink."
|
||||||
[{:keys [clip zoom picture-fps] sid :symbol isolate-id :isolate}]
|
[{:keys [clip zoom picture-fps] sid :symbol isolate-id :isolate}]
|
||||||
(let [sym (some-> (clip/symbol clip sid) (isolate isolate-id))
|
(let [sym (some-> (clip/symbol clip sid) (isolate isolate-id))
|
||||||
|
[width height] (clip/stage clip sid)
|
||||||
zoom (max 1 (js/Math.floor (or zoom 1)))]
|
zoom (max 1 (js/Math.floor (or zoom 1)))]
|
||||||
(when sym
|
(when sym
|
||||||
{:frames (:frames sym)
|
{:frames (:frames sym)
|
||||||
:fps (:fps clip)
|
:fps (:fps clip)
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:width (* (:width clip) zoom)
|
:width (* width zoom)
|
||||||
:height (* (:height clip) zoom)
|
:height (* height zoom)
|
||||||
:seconds (/ (:frames sym) (:fps clip))
|
:seconds (/ (:frames sym) (:fps clip))
|
||||||
;; The unedited picture-grid count. A per-instance pose track can add or
|
;; The unedited picture-grid count. A per-instance pose track can add or
|
||||||
;; remove changes, so this is only the grid's nominal count.
|
;; remove changes, so this is only the grid's nominal count.
|
||||||
|
|
@ -189,19 +190,20 @@
|
||||||
:symbols (vec (sort-by str (keys (:symbols clip))))})))
|
:symbols (vec (sort-by str (keys (:symbols clip))))})))
|
||||||
(let [{:keys [frames fps zoom]} (plan {:clip clip :symbol sid :zoom zoom
|
(let [{:keys [frames fps zoom]} (plan {:clip clip :symbol sid :zoom zoom
|
||||||
:isolate isolate-id})
|
:isolate isolate-id})
|
||||||
|
[width height] (clip/stage clip sid)
|
||||||
;; Rooted at the chosen symbol, as the stage is. Nested instances
|
;; Rooted at the chosen symbol, as the stage is. Nested instances
|
||||||
;; inside it still resolve — clip/resolver is the function that knows
|
;; inside it still resolve — clip/resolver is the function that knows
|
||||||
;; how.
|
;; how.
|
||||||
doc (assoc-in clip [:symbols sid] sym)
|
doc (assoc-in clip [:symbols sid] sym)
|
||||||
resolve-frame (clip/resolver doc store palette sid
|
resolve-frame (clip/resolver doc store palette sid
|
||||||
{:picture-fps picture-fps})
|
{:picture-fps picture-fps})
|
||||||
ras (raster/make (:width clip) (:height clip))
|
ras (raster/make width height)
|
||||||
bg (get palette :bg 0)]
|
bg (get palette :bg 0)]
|
||||||
(-> (audio! doc sid store audio-url)
|
(-> (audio! doc sid store audio-url)
|
||||||
(.then (fn [audio]
|
(.then (fn [audio]
|
||||||
(js/Promise.resolve
|
(js/Promise.resolve
|
||||||
(begin! exporter {:name name :width (:width clip)
|
(begin! exporter {:name name :width width
|
||||||
:height (:height clip) :zoom zoom
|
:height height :zoom zoom
|
||||||
:fps fps :frames frames :ramp ramp
|
:fps fps :frames frames :ramp ramp
|
||||||
:audio audio}))))
|
:audio audio}))))
|
||||||
(.then (fn [_]
|
(.then (fn [_]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
"Layer-2 extractors over the transport. Cheap by construction: each one reads a
|
"Layer-2 extractors over the transport. Cheap by construction: each one reads a
|
||||||
path and returns a value, so a tick that changes only `:frame` notifies only
|
path and returns a value, so a tick that changes only `:frame` notifies only
|
||||||
the things that asked for `:frame`."
|
the things that asked for `:frame`."
|
||||||
(:require [re-frame.core :as rf]))
|
(:require [arthur.domain.clip :as clip]
|
||||||
|
[arthur.footage.store :as store]
|
||||||
|
[re-frame.core :as rf]))
|
||||||
|
|
||||||
(rf/reg-sub ::frame (fn [db _] (get-in db [:playback :frame])))
|
(rf/reg-sub ::frame (fn [db _] (get-in db [:playback :frame])))
|
||||||
(rf/reg-sub ::playing? (fn [db _] (get-in db [:playback :playing?])))
|
(rf/reg-sub ::playing? (fn [db _] (get-in db [:playback :playing?])))
|
||||||
|
|
@ -11,11 +13,19 @@
|
||||||
(rf/reg-sub ::muted? (fn [db _] (get-in db [:playback :muted?])))
|
(rf/reg-sub ::muted? (fn [db _] (get-in db [:playback :muted?])))
|
||||||
(rf/reg-sub ::fps (fn [db _] (get-in db [:clip :fps])))
|
(rf/reg-sub ::fps (fn [db _] (get-in db [:clip :fps])))
|
||||||
(rf/reg-sub ::display-fps (fn [db _] (get-in db [:clip :display-fps])))
|
(rf/reg-sub ::display-fps (fn [db _] (get-in db [:clip :display-fps])))
|
||||||
;; The stage, in pixels. On the clip because project dimensions are independent
|
;; The open symbol can have a local stage size; otherwise it follows the project.
|
||||||
;; of the footage — see flow/freeze/face-placement — so the canvas and the raster
|
(rf/reg-sub ::stage-size
|
||||||
;; take their size from the document rather than from a constant.
|
:<- [:arthur.subs.playback/clip-id]
|
||||||
(rf/reg-sub ::width (fn [db _] (get-in db [:clip :width])))
|
:<- [:arthur.subs.playback/revision]
|
||||||
(rf/reg-sub ::height (fn [db _] (get-in db [:clip :height])))
|
:<- [:arthur.subs.playback/open]
|
||||||
|
(fn [[id _ sid] _]
|
||||||
|
(when-let [document (:clip (store/entry id))]
|
||||||
|
(clip/stage document sid))))
|
||||||
|
(rf/reg-sub ::clip-id (fn [db _] (:clip/current db)))
|
||||||
|
(rf/reg-sub ::revision (fn [db _] (:paint/revision db)))
|
||||||
|
(rf/reg-sub ::open (fn [db _] (get-in db [:ui :open])))
|
||||||
|
(rf/reg-sub ::width :<- [::stage-size] (fn [[w _] _] w))
|
||||||
|
(rf/reg-sub ::height :<- [::stage-size] (fn [[_ h] _] h))
|
||||||
(rf/reg-sub ::audio (fn [db _] (get-in db [:clip :audio])))
|
(rf/reg-sub ::audio (fn [db _] (get-in db [:clip :audio])))
|
||||||
(rf/reg-sub ::footage (fn [db _] (:footage db)))
|
(rf/reg-sub ::footage (fn [db _] (:footage db)))
|
||||||
;; The document's identity on the server. Not derived and not large — an id, a
|
;; The document's identity on the server. Not derived and not large — an id, a
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
has analysis in it. Nothing here computes — every control dispatches an intent
|
has analysis in it. Nothing here computes — every control dispatches an intent
|
||||||
and every readout comes off a subscription."
|
and every readout comes off a subscription."
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
|
[arthur.domain.clip :as clip-domain]
|
||||||
[arthur.domain.channel :as channel]
|
[arthur.domain.channel :as channel]
|
||||||
[arthur.domain.feature :as feature]
|
[arthur.domain.feature :as feature]
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
|
|
@ -38,6 +39,15 @@
|
||||||
(defn- section [title & body]
|
(defn- section [title & body]
|
||||||
(into [:section.section [:h2 title]] body))
|
(into [:section.section [:h2 title]] body))
|
||||||
|
|
||||||
|
(defn- number-field [label value on-change & [placeholder disabled?]]
|
||||||
|
[:label.inspector-field label
|
||||||
|
[:input {:type "number" :min 1 :step 1 :value (or value "")
|
||||||
|
:placeholder placeholder
|
||||||
|
:disabled disabled?
|
||||||
|
:on-change (fn [e]
|
||||||
|
(let [v (.. e -target -value)]
|
||||||
|
(on-change (when (seq v) (js/parseInt v 10)))))}]])
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; the clip
|
;; the clip
|
||||||
|
|
||||||
|
|
@ -47,15 +57,22 @@
|
||||||
picture @(rf/subscribe [::playback/display-fps])
|
picture @(rf/subscribe [::playback/display-fps])
|
||||||
open @(rf/subscribe [::render/open])
|
open @(rf/subscribe [::render/open])
|
||||||
frames @(rf/subscribe [::render/frames])
|
frames @(rf/subscribe [::render/frames])
|
||||||
expose @(rf/subscribe [::render/exposure])]
|
project @(rf/subscribe [::playback/project])
|
||||||
[section "clip"
|
busy? (:busy? project)]
|
||||||
|
[section "project"
|
||||||
[facts
|
[facts
|
||||||
"name" (:name clip)
|
"name" (or (:name project) (:name clip))
|
||||||
"stage" (str (:width clip) "×" (:height clip))
|
"stage" (str (:width clip) "×" (:height clip))
|
||||||
"open" (some-> open name)
|
"open" (some-> open name)
|
||||||
"length" (str frames " frames")
|
"length" (str frames " frames")
|
||||||
"source" (str fps " fps")
|
"rate" (str fps " fps")]
|
||||||
"expose" (str "on " expose "s")]
|
[:div.inspector-form
|
||||||
|
[number-field "width" (:width clip)
|
||||||
|
#(rf/dispatch [::project/project-setting :width %]) nil busy?]
|
||||||
|
[number-field "height" (:height clip)
|
||||||
|
#(rf/dispatch [::project/project-setting :height %]) nil busy?]
|
||||||
|
[number-field "project fps" fps
|
||||||
|
#(rf/dispatch [::project/project-setting :fps %]) nil busy?]]
|
||||||
;; Sampling the frozen roto at a lower rate. The source track, the duration
|
;; Sampling the frozen roto at a lower rate. The source track, the duration
|
||||||
;; and the audio clock are untouched — a drawing is HELD, the file is never
|
;; and the audio clock are untouched — a drawing is HELD, the file is never
|
||||||
;; short — which is why this is a picture rate and not a playback rate.
|
;; short — which is why this is a picture rate and not a playback rate.
|
||||||
|
|
@ -190,12 +207,28 @@
|
||||||
;; a symbol
|
;; a symbol
|
||||||
|
|
||||||
(defn- symbol-section [sid]
|
(defn- symbol-section [sid]
|
||||||
(let [sym (get-in @(rf/subscribe [::render/clip]) [:symbols sid])]
|
(let [clip @(rf/subscribe [::render/clip])
|
||||||
|
sym (get-in clip [:symbols sid])
|
||||||
|
busy? (:busy? @(rf/subscribe [::playback/project]))]
|
||||||
[section "symbol"
|
[section "symbol"
|
||||||
[facts
|
[facts
|
||||||
"id" (str sid)
|
"id" (str sid)
|
||||||
"length" (str (:frames sym) " frames")
|
"length" (str (:frames sym) " frames")
|
||||||
"nodes" (str (count (:nodes sym)))]]))
|
"stage" (let [[w h] (clip-domain/stage clip sid)] (str w "×" h))
|
||||||
|
"nodes" (str (count (:nodes sym)))]
|
||||||
|
[:div.inspector-form
|
||||||
|
[number-field "length (frames)" (:frames sym)
|
||||||
|
#(rf/dispatch [::project/symbol-setting sid :frames %]) nil busy?]
|
||||||
|
[number-field "width" (:width sym)
|
||||||
|
#(rf/dispatch [::project/symbol-setting sid :width %]) "project default" busy?]
|
||||||
|
[number-field "height" (:height sym)
|
||||||
|
#(rf/dispatch [::project/symbol-setting sid :height %]) "project default" busy?]
|
||||||
|
(when (or (:width sym) (:height sym))
|
||||||
|
[:div.row
|
||||||
|
[:button {:disabled busy? :on-click #(do
|
||||||
|
(rf/dispatch [::project/symbol-setting sid :width nil])
|
||||||
|
(rf/dispatch [::project/symbol-setting sid :height nil]))}
|
||||||
|
"use project stage"]])]]))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; tracked objects
|
;; tracked objects
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue