@cardstack/choreo 0.0.0 → 0.1.0-unstable.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +55 -6
- package/addon-main.cjs +4 -0
- package/declarations/anchors.d.ts +26 -0
- package/declarations/anchors.d.ts.map +1 -0
- package/declarations/arming.d.ts +33 -0
- package/declarations/arming.d.ts.map +1 -0
- package/declarations/beacon.d.ts +9 -0
- package/declarations/beacon.d.ts.map +1 -0
- package/declarations/beacons.d.ts +23 -0
- package/declarations/beacons.d.ts.map +1 -0
- package/declarations/changeset.d.ts +46 -0
- package/declarations/changeset.d.ts.map +1 -0
- package/declarations/choreo.d.ts +261 -0
- package/declarations/choreo.d.ts.map +1 -0
- package/declarations/compile.d.ts +45 -0
- package/declarations/compile.d.ts.map +1 -0
- package/declarations/deliver.d.ts +44 -0
- package/declarations/deliver.d.ts.map +1 -0
- package/declarations/easings.d.ts +20 -0
- package/declarations/easings.d.ts.map +1 -0
- package/declarations/far.d.ts +33 -0
- package/declarations/far.d.ts.map +1 -0
- package/declarations/film/clip.d.ts +51 -0
- package/declarations/film/clip.d.ts.map +1 -0
- package/declarations/film/clips.d.ts +155 -0
- package/declarations/film/clips.d.ts.map +1 -0
- package/declarations/film/film.d.ts +1208 -0
- package/declarations/film/film.d.ts.map +1 -0
- package/declarations/film/graph/adjust.d.ts +112 -0
- package/declarations/film/graph/adjust.d.ts.map +1 -0
- package/declarations/film/graph/compile.d.ts +140 -0
- package/declarations/film/graph/compile.d.ts.map +1 -0
- package/declarations/film/graph/host.d.ts +67 -0
- package/declarations/film/graph/host.d.ts.map +1 -0
- package/declarations/film/graph/nodes.d.ts +287 -0
- package/declarations/film/graph/nodes.d.ts.map +1 -0
- package/declarations/film/index.d.ts +24 -0
- package/declarations/film/index.d.ts.map +1 -0
- package/declarations/film/joins.d.ts +143 -0
- package/declarations/film/joins.d.ts.map +1 -0
- package/declarations/film/math.d.ts +15 -0
- package/declarations/film/math.d.ts.map +1 -0
- package/declarations/film/overlays.d.ts +54 -0
- package/declarations/film/overlays.d.ts.map +1 -0
- package/declarations/film/picture.d.ts +104 -0
- package/declarations/film/picture.d.ts.map +1 -0
- package/declarations/film/plate.d.ts +37 -0
- package/declarations/film/plate.d.ts.map +1 -0
- package/declarations/film/player.d.ts +120 -0
- package/declarations/film/player.d.ts.map +1 -0
- package/declarations/film/rail.d.ts +40 -0
- package/declarations/film/rail.d.ts.map +1 -0
- package/declarations/film/schedule.d.ts +93 -0
- package/declarations/film/schedule.d.ts.map +1 -0
- package/declarations/film/seam.d.ts +38 -0
- package/declarations/film/seam.d.ts.map +1 -0
- package/declarations/film/titles.d.ts +37 -0
- package/declarations/film/titles.d.ts.map +1 -0
- package/declarations/film/types.d.ts +544 -0
- package/declarations/film/types.d.ts.map +1 -0
- package/declarations/film.d.ts +3 -0
- package/declarations/film.d.ts.map +1 -0
- package/declarations/gesture.d.ts +33 -0
- package/declarations/gesture.d.ts.map +1 -0
- package/declarations/index.d.ts +26 -0
- package/declarations/index.d.ts.map +1 -0
- package/declarations/measure.d.ts +29 -0
- package/declarations/measure.d.ts.map +1 -0
- package/declarations/path.d.ts +73 -0
- package/declarations/path.d.ts.map +1 -0
- package/declarations/registry.d.ts +46 -0
- package/declarations/registry.d.ts.map +1 -0
- package/declarations/run.d.ts +406 -0
- package/declarations/run.d.ts.map +1 -0
- package/declarations/space.d.ts +31 -0
- package/declarations/space.d.ts.map +1 -0
- package/declarations/steps.d.ts +479 -0
- package/declarations/steps.d.ts.map +1 -0
- package/declarations/test-support/index.d.ts +30 -0
- package/declarations/test-support/index.d.ts.map +1 -0
- package/declarations/types.d.ts +761 -0
- package/declarations/types.d.ts.map +1 -0
- package/dist/anchors.js +34 -0
- package/dist/anchors.js.map +1 -0
- package/dist/arming.js +120 -0
- package/dist/arming.js.map +1 -0
- package/dist/beacon.js +25 -0
- package/dist/beacon.js.map +1 -0
- package/dist/beacons.js +77 -0
- package/dist/beacons.js.map +1 -0
- package/dist/changeset.js +129 -0
- package/dist/changeset.js.map +1 -0
- package/dist/choreo.js +1026 -0
- package/dist/choreo.js.map +1 -0
- package/dist/compile.js +1403 -0
- package/dist/compile.js.map +1 -0
- package/dist/deliver.js +326 -0
- package/dist/deliver.js.map +1 -0
- package/dist/easings.js +39 -0
- package/dist/easings.js.map +1 -0
- package/dist/far.js +147 -0
- package/dist/far.js.map +1 -0
- package/dist/film/clip.js +100 -0
- package/dist/film/clip.js.map +1 -0
- package/dist/film/clips.js +147 -0
- package/dist/film/clips.js.map +1 -0
- package/dist/film/film.js +4424 -0
- package/dist/film/film.js.map +1 -0
- package/dist/film/graph/adjust.js +160 -0
- package/dist/film/graph/adjust.js.map +1 -0
- package/dist/film/graph/compile.js +225 -0
- package/dist/film/graph/compile.js.map +1 -0
- package/dist/film/graph/host.js +77 -0
- package/dist/film/graph/host.js.map +1 -0
- package/dist/film/graph/nodes.js +500 -0
- package/dist/film/graph/nodes.js.map +1 -0
- package/dist/film/index.js +18 -0
- package/dist/film/index.js.map +1 -0
- package/dist/film/joins.js +260 -0
- package/dist/film/joins.js.map +1 -0
- package/dist/film/math.js +49 -0
- package/dist/film/math.js.map +1 -0
- package/dist/film/overlays.js +66 -0
- package/dist/film/overlays.js.map +1 -0
- package/dist/film/picture.js +58 -0
- package/dist/film/picture.js.map +1 -0
- package/dist/film/plate.js +36 -0
- package/dist/film/plate.js.map +1 -0
- package/dist/film/player.js +79 -0
- package/dist/film/player.js.map +1 -0
- package/dist/film/rail.js +38 -0
- package/dist/film/rail.js.map +1 -0
- package/dist/film/schedule.js +238 -0
- package/dist/film/schedule.js.map +1 -0
- package/dist/film/seam.js +64 -0
- package/dist/film/seam.js.map +1 -0
- package/dist/film/titles.js +45 -0
- package/dist/film/titles.js.map +1 -0
- package/dist/film/types.js +2 -0
- package/dist/film/types.js.map +1 -0
- package/dist/film.js +18 -0
- package/dist/film.js.map +1 -0
- package/dist/gesture.js +111 -0
- package/dist/gesture.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/measure.js +99 -0
- package/dist/measure.js.map +1 -0
- package/dist/path.js +272 -0
- package/dist/path.js.map +1 -0
- package/dist/registry.js +55 -0
- package/dist/registry.js.map +1 -0
- package/dist/run.js +2423 -0
- package/dist/run.js.map +1 -0
- package/dist/space.js +57 -0
- package/dist/space.js.map +1 -0
- package/dist/steps.js +831 -0
- package/dist/steps.js.map +1 -0
- package/dist/test-support/index.js +150 -0
- package/dist/test-support/index.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +201 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.js","sources":["../src/deliver.ts"],"sourcesContent":["/**\n * Text delivery — `@by='word' | 'character' | 'paragraph'` (§4.3).\n *\n * The split is a run-time costume, not a render: the sprite's own text\n * nodes are lifted out whole and kept (Glimmer still owns them — a tracked\n * update mid-flight writes into the detached node and comes back at\n * restore), stand-in spans deliver the animation, and the restore puts the\n * original nodes back exactly where they were. Assistive tech reads the\n * full text throughout: the container carries it as a label while the\n * chopped-up copy is hidden.\n *\n * Slots play on the platform's own animations (WAAPI), not the engine:\n * they are ephemeral, their schedule is computed once, and a spring's\n * clock arrives pre-sampled through the generator — the same shape the\n * native driver uses for everything.\n */\nimport {\n calcGeneratorDuration,\n generateLinearEasing,\n maxGeneratorDuration,\n spring,\n} from 'motion-dom';\n\nimport type {\n Cue,\n DeliveryBy,\n Easing,\n PropValue,\n SpringSpec,\n} from './types.ts';\n\ntype EasingFunction = (t: number) => number;\nimport { ladder } from './compile.ts';\n\ninterface Slot {\n el: HTMLElement;\n}\n\ninterface Split {\n /** put the original nodes back; safe to call twice */\n restore(): void;\n slots: Slot[];\n}\n\nconst spanFor = (text: string): HTMLElement => {\n const span = document.createElement('span');\n span.textContent = text;\n span.setAttribute('aria-hidden', 'true');\n span.style.display = 'inline-block';\n span.style.whiteSpace = 'pre';\n return span;\n};\n\nfunction textNodesOf(root: HTMLElement): Text[] {\n const out: Text[] = [];\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n for (let n = walker.nextNode(); n; n = walker.nextNode()) {\n if ((n.textContent ?? '').length) {\n out.push(n as Text);\n }\n }\n return out;\n}\n\nfunction unitsOf(text: string, by: 'character' | 'word'): string[] {\n if (by === 'character') {\n return Array.from(text);\n }\n // words keep their trailing whitespace so the line reassembles exactly\n return text.match(/\\S+\\s*|\\s+/g) ?? [];\n}\n\nexport function split(el: HTMLElement, by: Exclude<DeliveryBy, 'item'>): Split {\n if (by === 'paragraph') {\n // each element child is a slot; nothing is rewritten\n const slots = Array.from(el.children).filter(\n (c): c is HTMLElement => c instanceof HTMLElement,\n );\n if (!slots.length) {\n throw new Error(\n \"choreo: @by='paragraph' needs child elements to deliver\",\n );\n }\n return { restore() {}, slots: slots.map((s) => ({ el: s })) };\n }\n const originals = textNodesOf(el);\n if (!originals.length) {\n throw new Error(`choreo: @by='${by}' needs text to deliver`);\n }\n // The costume must not change the body's shape. The stand-in spans wear\n // `white-space: pre`, so whitespace the container was COLLAPSING — a\n // template's newline and indentation are real text nodes — would suddenly\n // take width, and a newline would become a hard break: the line jumps\n // aside for its own delivery and snaps back at restore. Collapse the way\n // the element's own computed white-space does before cutting units.\n const ws = getComputedStyle(el).whiteSpace;\n const collapsing = ws === 'normal' || ws === 'nowrap' || ws === 'pre-line';\n const textOf = (node: Text, index: number): string => {\n let text = node.textContent ?? '';\n if (collapsing) {\n text = text.replace(/\\s+/g, ' ');\n if (index === 0) {\n text = text.replace(/^ /, '');\n }\n if (index === originals.length - 1) {\n text = text.replace(/ $/, '');\n }\n }\n return text;\n };\n const label = el.getAttribute('aria-label');\n el.setAttribute('aria-label', el.textContent ?? '');\n const slots: Slot[] = [];\n const undo: (() => void)[] = [];\n for (const [index, node] of originals.entries()) {\n const parent = node.parentNode!;\n const marker = document.createComment('choreo-delivery');\n parent.replaceChild(marker, node);\n const spans: HTMLElement[] = [];\n for (const unit of unitsOf(textOf(node, index), by)) {\n const span = spanFor(unit);\n parent.insertBefore(span, marker);\n spans.push(span);\n if (by === 'character' && /^\\s+$/.test(unit)) {\n continue; // whitespace travels, but is not a slot of its own\n }\n slots.push({ el: span });\n }\n undo.push(() => {\n for (const span of spans) {\n span.remove();\n }\n marker.parentNode?.replaceChild(node, marker);\n });\n }\n let restored = false;\n return {\n restore() {\n if (restored) {\n return;\n }\n restored = true;\n undo.reverse().forEach((fn) => fn());\n if (label === null) {\n el.removeAttribute('aria-label');\n } else {\n el.setAttribute('aria-label', label);\n }\n },\n slots,\n };\n}\n\n/* ---- the slot schedule: windows inside the step's span (§4.3) ---- */\n\nexport interface Window {\n at: number;\n ms: number;\n}\n\n/**\n * `@stagger` seconds between starts, window = what remains of the span; with\n * no stagger, the Build Order demo's policy: a 0.55-of-span window with the\n * starts distributed across the remainder.\n */\nexport function windows(n: number, span: number, stagger: number): Window[] {\n if (n <= 1 || span <= 0) {\n return Array.from({ length: n }, () => ({ at: 0, ms: span }));\n }\n if (stagger > 0) {\n const ms = Math.max(1, span - (n - 1) * stagger);\n return Array.from({ length: n }, (_, i) => ({ at: i * stagger, ms }));\n }\n const ms = span * 0.55;\n return Array.from({ length: n }, (_, i) => ({\n at: (i * (span - ms)) / (n - 1),\n ms,\n }));\n}\n\n/* ---- keyframes and easing for the platform's animations ---- */\n\nconst UNITLESS = new Set(['opacity', 'scale', 'scaleX', 'scaleY']);\nconst TRANSFORMS: Record<string, (v: string) => string> = {\n rotate: (v) => `rotate(${v}deg)`,\n scale: (v) => `scale(${v})`,\n x: (v) => `translateX(${v}px)`,\n y: (v) => `translateY(${v}px)`,\n};\n\n/** cue.target, as WAAPI property-indexed keyframes */\nexport function keyframesOf(\n target: Record<string, unknown>,\n times?: number[],\n ease?: string,\n): PropertyIndexedKeyframes {\n const out: Record<string, string[]> = {};\n const transforms = new Map<string, string[]>();\n for (const key in target) {\n const raw = target[key];\n const values = (Array.isArray(raw) ? raw : [raw]).map(String);\n if (TRANSFORMS[key]) {\n transforms.set(key, values);\n } else {\n out[UNITLESS.has(key) || isNaN(Number(values[0])) ? key : key] = values;\n }\n }\n if (transforms.size) {\n const length = Math.max(...[...transforms.values()].map((v) => v.length));\n const frames: string[] = [];\n for (let i = 0; i < length; i++) {\n const parts: string[] = [];\n for (const [key, values] of transforms) {\n parts.push(TRANSFORMS[key]!(values[Math.min(i, values.length - 1)]!));\n }\n frames.push(parts.join(' '));\n }\n out['transform'] = frames;\n }\n return times\n ? { ...out, offset: times, easing: times.map(() => ease ?? 'linear') }\n : out;\n}\n\nconst NAMED: Record<string, string> = {\n // the overshoots as cubic-beziers, so a text delivery may Pop: these are\n // motion's own anticipate/back curves flattened to one bend\n anticipate: 'cubic-bezier(0.68,-0.6,0.32,1.6)',\n backIn: 'cubic-bezier(0.36,0,0.66,-0.56)',\n backInOut: 'cubic-bezier(0.68,-0.6,0.32,1.6)',\n backOut: 'cubic-bezier(0.34,1.56,0.64,1)',\n circIn: 'cubic-bezier(0.55,0,1,0.45)',\n circInOut: 'cubic-bezier(0.85,0,0.15,1)',\n circOut: 'cubic-bezier(0,0.55,0.45,1)',\n easeIn: 'ease-in',\n easeInOut: 'ease-in-out',\n easeOut: 'ease-out',\n linear: 'linear',\n};\n\nexport function cssEasing(ease: Easing | undefined, ms: number): string {\n if (ease === undefined) {\n return 'ease-in-out';\n }\n if (typeof ease === 'string') {\n return NAMED[ease] ?? 'ease-in-out';\n }\n if (Array.isArray(ease)) {\n return `cubic-bezier(${ease.join(',')})`;\n }\n return generateLinearEasing(ease as EasingFunction, ms);\n}\n\n/** a spring's clock, pre-sampled: its real duration and a linear() easing */\nexport function springEasing(spec: SpringSpec | undefined): {\n easing: string;\n ms: number;\n} {\n const generator = spring({ keyframes: [0, 100], ...(spec ?? {}) });\n const ms = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);\n const progress: EasingFunction = (t: number) =>\n generator.next(t * ms).value / 100;\n return { easing: generateLinearEasing(progress, ms), ms };\n}\n\nexport interface Delivery {\n cancel(): void;\n /** finish NOW: land the end values and give the text back — a scrub past\n * the cue's end must not leave paused spans standing in for the sprite */\n complete(): void;\n finished: Promise<void>;\n pause(): void;\n play(): void;\n /** place the whole delivery at `ms` past its cue start (unscaled) */\n seek(ms: number): void;\n speed(rate: number): void;\n}\n\n/** play one delivery cue: split, animate the slots, restore, land the end values */\nexport function deliver(cue: Cue, speed: number): Delivery {\n const { by, order, stagger } = cue.delivery!;\n if (by === 'item') {\n throw new Error('choreo: item delivery is the ladder, not a split');\n }\n const parts = split(cue.sprite.element, by);\n const ranks = ladder(parts.slots.length, order);\n const spans = windows(parts.slots.length, cue.duration, stagger);\n const times = cue.transition?.times as number[] | undefined;\n const keyframes = keyframesOf(\n cue.target ?? {},\n times,\n cssEasing(cue.transition?.ease as Easing | undefined, cue.duration),\n );\n const isSpring = cue.kind === 'spring';\n const sprung = isSpring\n ? springEasing(cue.transition as SpringSpec | undefined)\n : undefined;\n const animations: Animation[] = [];\n for (const [index, slot] of parts.slots.entries()) {\n const w = spans[ranks[index] ?? index]!;\n const ms = sprung ? Math.min(sprung.ms, w.ms) : w.ms;\n animations.push(\n slot.el.animate(keyframes as PropertyIndexedKeyframes, {\n delay: w.at * speed,\n duration: Math.max(1, ms * speed),\n easing: times\n ? 'linear'\n : sprung\n ? sprung.easing\n : cssEasing(\n (cue.transition as { ease?: Easing } | undefined)?.ease,\n ms,\n ),\n fill: 'both',\n }),\n );\n }\n let done = false;\n const land = () => {\n if (done) {\n return;\n }\n done = true;\n // the whole sprite sits at the delivery's end values once the slots go\n const ve = cue.sprite.node.visualElement;\n if (ve && cue.target) {\n for (const key in cue.target) {\n const raw = cue.target[key];\n const end = Array.isArray(raw) ? raw[raw.length - 1] : raw;\n ve.getValue(key, end as PropValue)!.jump(end as PropValue);\n }\n ve.render();\n }\n parts.restore();\n };\n const finished = Promise.all(\n animations.map((a) => a.finished.catch(() => {})),\n ).then(land);\n return {\n cancel() {\n done = true; // interrupted: original nodes back, stylesheet's values\n animations.forEach((a) => a.cancel());\n parts.restore();\n },\n complete() {\n animations.forEach((a) => {\n try {\n a.finish();\n } catch {\n a.cancel();\n }\n });\n land();\n },\n finished,\n pause() {\n animations.forEach((a) => a.pause());\n },\n play() {\n animations.forEach((a) => a.play());\n },\n seek(ms: number) {\n const t = Math.max(0, ms * speed);\n animations.forEach((a) => (a.currentTime = t));\n },\n speed(rate: number) {\n animations.forEach((a) => (a.playbackRate = rate));\n },\n };\n}\n"],"names":["spanFor","text","span","document","createElement","textContent","setAttribute","style","display","whiteSpace","textNodesOf","root","out","walker","createTreeWalker","NodeFilter","SHOW_TEXT","n","nextNode","length","push","unitsOf","by","Array","from","match","split","el","slots","children","filter","c","HTMLElement","Error","restore","map","s","originals","ws","getComputedStyle","collapsing","textOf","node","index","replace","label","getAttribute","undo","entries","parent","parentNode","marker","createComment","replaceChild","spans","unit","insertBefore","test","remove","restored","reverse","forEach","fn","removeAttribute","windows","stagger","at","ms","Math","max","_","i","UNITLESS","Set","TRANSFORMS","rotate","v","scale","x","y","keyframesOf","target","times","ease","transforms","Map","key","raw","values","isArray","String","set","has","isNaN","Number","size","frames","parts","min","join","offset","easing","NAMED","anticipate","backIn","backInOut","backOut","circIn","circInOut","circOut","easeIn","easeInOut","easeOut","linear","cssEasing","undefined","generateLinearEasing","springEasing","spec","generator","spring","keyframes","calcGeneratorDuration","maxGeneratorDuration","progress","t","next","value","deliver","cue","speed","order","delivery","sprite","element","ranks","ladder","duration","transition","isSpring","kind","sprung","animations","slot","w","animate","delay","fill","done","land","ve","visualElement","end","getValue","jump","render","finished","Promise","all","a","catch","then","cancel","complete","finish","pause","play","seek","currentTime","rate","playbackRate"],"mappings":"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"}
|
package/dist/easings.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cosine easings, recovered from boxel-motion (`easings/cosine.ts` at
|
|
3
|
+
* `cardstack/boxel@394503e3b7^`) — the ones its own flagship transition used.
|
|
4
|
+
*
|
|
5
|
+
* `easeInAndOut` is the interesting one: a raised cosine has zero slope at both
|
|
6
|
+
* ends, so an animation interrupted at t=0 inherits none of its own curve and
|
|
7
|
+
* all of whatever it is replacing. That is the property Ember Animated leans on
|
|
8
|
+
* to make a summed corrective curve continuous; here the same shape is simply
|
|
9
|
+
* available as an `@ease`.
|
|
10
|
+
*
|
|
11
|
+
* <c.Tween @of={{c.kept 'card'}} @opacity={{1}} @ms={{300}} @ease={{easeIn}} />
|
|
12
|
+
*
|
|
13
|
+
* `@ease` also takes an engine easing name or a cubic-bezier as four numbers;
|
|
14
|
+
* these exist because a bezier cannot express a raised cosine exactly.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** a raised cosine: zero slope at both ends */
|
|
18
|
+
function easeInAndOut(t) {
|
|
19
|
+
return 0.5 - Math.cos(t * Math.PI) / 2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cosine in, then straight. Switching naively from cosine to linear at the
|
|
24
|
+
* halfway point would finish early, so the curve is rescaled to stay inside
|
|
25
|
+
* the 0..1 window.
|
|
26
|
+
*/
|
|
27
|
+
const adjust = 1 / 2 + 1 / Math.PI;
|
|
28
|
+
const cutover = 1 / (2 * adjust);
|
|
29
|
+
const intercept = (2 - Math.PI) / 4;
|
|
30
|
+
const slope = Math.PI / 2 * adjust;
|
|
31
|
+
function easeIn(t) {
|
|
32
|
+
return t < cutover ? easeInAndOut(t * adjust) : slope * t + intercept;
|
|
33
|
+
}
|
|
34
|
+
function easeOut(t) {
|
|
35
|
+
return 1 - easeIn(1 - t);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { easeIn, easeInAndOut, easeOut };
|
|
39
|
+
//# sourceMappingURL=easings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"easings.js","sources":["../src/easings.ts"],"sourcesContent":["/**\n * The cosine easings, recovered from boxel-motion (`easings/cosine.ts` at\n * `cardstack/boxel@394503e3b7^`) — the ones its own flagship transition used.\n *\n * `easeInAndOut` is the interesting one: a raised cosine has zero slope at both\n * ends, so an animation interrupted at t=0 inherits none of its own curve and\n * all of whatever it is replacing. That is the property Ember Animated leans on\n * to make a summed corrective curve continuous; here the same shape is simply\n * available as an `@ease`.\n *\n * <c.Tween @of={{c.kept 'card'}} @opacity={{1}} @ms={{300}} @ease={{easeIn}} />\n *\n * `@ease` also takes an engine easing name or a cubic-bezier as four numbers;\n * these exist because a bezier cannot express a raised cosine exactly.\n */\n\n/** a raised cosine: zero slope at both ends */\nexport function easeInAndOut(t: number): number {\n return 0.5 - Math.cos(t * Math.PI) / 2;\n}\n\n/**\n * Cosine in, then straight. Switching naively from cosine to linear at the\n * halfway point would finish early, so the curve is rescaled to stay inside\n * the 0..1 window.\n */\nconst adjust = 1 / 2 + 1 / Math.PI;\nconst cutover = 1 / (2 * adjust);\nconst intercept = (2 - Math.PI) / 4;\nconst slope = (Math.PI / 2) * adjust;\n\nexport function easeIn(t: number): number {\n return t < cutover ? easeInAndOut(t * adjust) : slope * t + intercept;\n}\n\nexport function easeOut(t: number): number {\n return 1 - easeIn(1 - t);\n}\n"],"names":["easeInAndOut","t","Math","cos","PI","adjust","cutover","intercept","slope","easeIn","easeOut"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACO,SAASA,YAAYA,CAACC,CAAS,EAAU;AAC9C,EAAA,OAAO,GAAG,GAAGC,IAAI,CAACC,GAAG,CAACF,CAAC,GAAGC,IAAI,CAACE,EAAE,CAAC,GAAG,CAAC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAMC,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAGH,IAAI,CAACE,EAAE;AAClC,MAAME,OAAO,GAAG,CAAC,IAAI,CAAC,GAAGD,MAAM,CAAC;AAChC,MAAME,SAAS,GAAG,CAAC,CAAC,GAAGL,IAAI,CAACE,EAAE,IAAI,CAAC;AACnC,MAAMI,KAAK,GAAIN,IAAI,CAACE,EAAE,GAAG,CAAC,GAAIC,MAAM;AAE7B,SAASI,MAAMA,CAACR,CAAS,EAAU;AACxC,EAAA,OAAOA,CAAC,GAAGK,OAAO,GAAGN,YAAY,CAACC,CAAC,GAAGI,MAAM,CAAC,GAAGG,KAAK,GAAGP,CAAC,GAAGM,SAAS;AACvE;AAEO,SAASG,OAAOA,CAACT,CAAS,EAAU;AACzC,EAAA,OAAO,CAAC,GAAGQ,MAAM,CAAC,CAAC,GAAGR,CAAC,CAAC;AAC1B;;;;"}
|
package/dist/far.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { registerBusyProbe } from 'glimmer-motion';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Far matching — one identity, two regions.
|
|
5
|
+
*
|
|
6
|
+
* A `<Choreo>` is a scene: it reconciles its own participants and nothing else.
|
|
7
|
+
* That is the point of nesting, and it is why an element that leaves one region
|
|
8
|
+
* and appears in another looks like a death and an unrelated birth rather than
|
|
9
|
+
* one continuous flight.
|
|
10
|
+
*
|
|
11
|
+
* Ember Animated calls the two halves `sentSprites` and `receivedSprites` and
|
|
12
|
+
* pairs them at a `farMatch` rendezvous — every animator that is starting this
|
|
13
|
+
* pass parks, they all wake together, and each reads the others' lists. The
|
|
14
|
+
* legacy boxel-motion described this but never built it.
|
|
15
|
+
*
|
|
16
|
+
* The rendezvous here is a barrier in the render pass itself, not a timed one.
|
|
17
|
+
* Every region that will animate this pass announces itself while Glimmer is
|
|
18
|
+
* still rendering (`join`), the first announcement books one post-render
|
|
19
|
+
* callback for all of them, and that callback runs three phases in order:
|
|
20
|
+
*
|
|
21
|
+
* 1. MEASURE — every region builds its changeset. Nobody has run anything,
|
|
22
|
+
* so nobody's in-flight values are polluting anybody else's measurement.
|
|
23
|
+
* 2. MATCH — inserted ids are paired against removed ids in OTHER regions.
|
|
24
|
+
* 3. RUN — every region compiles and plays its own timeline.
|
|
25
|
+
*
|
|
26
|
+
* All three are synchronous, so no frame is painted between a region measuring
|
|
27
|
+
* and a region pinning its start values. That matters: the whole reason cues
|
|
28
|
+
* pin synchronously is that one frame of the destination layout is visible
|
|
29
|
+
* otherwise, and a barrier that yielded would hand that frame back.
|
|
30
|
+
*
|
|
31
|
+
* The pairing itself is the same shape as the counterpart matching a region
|
|
32
|
+
* already does within itself. The receiving element is the one that flies: it
|
|
33
|
+
* takes the sender's bounds as an `initial` it never had, and the sender is
|
|
34
|
+
* dropped rather than orphaned, because the thing it would animate is already
|
|
35
|
+
* being animated somewhere else.
|
|
36
|
+
*
|
|
37
|
+
* Bounds cross the boundary in PAGE space, which is the only space the two
|
|
38
|
+
* regions agree on — a region-relative measurement means nothing to a region
|
|
39
|
+
* somewhere else on the page.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** one region's measured pass, waiting for the others before it runs */
|
|
43
|
+
|
|
44
|
+
const intents = new Set();
|
|
45
|
+
let booked = false;
|
|
46
|
+
/** a seam for the region to book the callback without far.ts importing the scheduler */
|
|
47
|
+
let schedule = fn => fn();
|
|
48
|
+
function setPassScheduler(fn) {
|
|
49
|
+
schedule = fn;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Announce that this region has a pass coming. Called while Glimmer is still
|
|
54
|
+
* rendering, so by the time the barrier runs, every region taking part in this
|
|
55
|
+
* pass is known — which is what lets the match be synchronous.
|
|
56
|
+
*/
|
|
57
|
+
function join(host) {
|
|
58
|
+
intents.add(host);
|
|
59
|
+
if (!booked) {
|
|
60
|
+
booked = true;
|
|
61
|
+
schedule(runBarrier);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function leave(host) {
|
|
65
|
+
intents.delete(host);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** a booked barrier is work outstanding: nothing has measured yet */
|
|
69
|
+
registerBusyProbe(() => booked && 'far-match barrier booked');
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Drop a barrier that will never run. A test torn down between `join` and the
|
|
73
|
+
* post-render callback leaves a destroyed region parked in `intents`, and the
|
|
74
|
+
* next test's first pass would try to measure it. `setupChoreo(hooks)` runs
|
|
75
|
+
* this before and after every test.
|
|
76
|
+
*/
|
|
77
|
+
function resetBarrier() {
|
|
78
|
+
intents.clear();
|
|
79
|
+
booked = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** mount everything rendered this pass before ANY region measures */
|
|
83
|
+
let beforeMeasure = () => {};
|
|
84
|
+
function setBeforeMeasure(fn) {
|
|
85
|
+
beforeMeasure = fn;
|
|
86
|
+
}
|
|
87
|
+
function runBarrier() {
|
|
88
|
+
booked = false;
|
|
89
|
+
const hosts = [...intents];
|
|
90
|
+
intents.clear();
|
|
91
|
+
beforeMeasure();
|
|
92
|
+
const passes = [];
|
|
93
|
+
for (const host of hosts) {
|
|
94
|
+
const pass = host.measurePass();
|
|
95
|
+
if (pass) {
|
|
96
|
+
passes.push(pass);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (passes.length > 1) {
|
|
100
|
+
match(passes);
|
|
101
|
+
}
|
|
102
|
+
for (const pass of passes) {
|
|
103
|
+
pass.host.finishPass(pass);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const deltaOf = s => s.initial && s.final ? {
|
|
107
|
+
height: s.final.page.height - s.initial.page.height,
|
|
108
|
+
width: s.final.page.width - s.initial.page.width,
|
|
109
|
+
x: s.final.page.x - s.initial.page.x,
|
|
110
|
+
y: s.final.page.y - s.initial.page.y
|
|
111
|
+
} : undefined;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Pair an id inserted in one region against the same id removed in another.
|
|
115
|
+
*
|
|
116
|
+
* The receiver becomes a kept sprite carrying the sender as its counterpart —
|
|
117
|
+
* exactly what same-region counterpart matching produces, so every step that
|
|
118
|
+
* already understands `kept` and `counterpart` works across the boundary with
|
|
119
|
+
* no further teaching. The sender is flagged `sent` so its own region knows to
|
|
120
|
+
* let it go quietly instead of playing a removal for it.
|
|
121
|
+
*/
|
|
122
|
+
function match(passes) {
|
|
123
|
+
for (const pass of passes) {
|
|
124
|
+
for (const s of pass.inserted) {
|
|
125
|
+
if (s.id === null || s.counterpart) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const other of passes) {
|
|
129
|
+
if (other === pass) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const sender = other.removed.find(r => r.id === s.id && !r.counterpart && !r.sent);
|
|
133
|
+
if (sender) {
|
|
134
|
+
sender.sent = true;
|
|
135
|
+
s.counterpart = sender;
|
|
136
|
+
s.initial = sender.initial;
|
|
137
|
+
s.type = 'kept';
|
|
138
|
+
s.delta = deltaOf(s);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { join, leave, resetBarrier, setBeforeMeasure, setPassScheduler };
|
|
147
|
+
//# sourceMappingURL=far.js.map
|
package/dist/far.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"far.js","sources":["../src/far.ts"],"sourcesContent":["/**\n * Far matching — one identity, two regions.\n *\n * A `<Choreo>` is a scene: it reconciles its own participants and nothing else.\n * That is the point of nesting, and it is why an element that leaves one region\n * and appears in another looks like a death and an unrelated birth rather than\n * one continuous flight.\n *\n * Ember Animated calls the two halves `sentSprites` and `receivedSprites` and\n * pairs them at a `farMatch` rendezvous — every animator that is starting this\n * pass parks, they all wake together, and each reads the others' lists. The\n * legacy boxel-motion described this but never built it.\n *\n * The rendezvous here is a barrier in the render pass itself, not a timed one.\n * Every region that will animate this pass announces itself while Glimmer is\n * still rendering (`join`), the first announcement books one post-render\n * callback for all of them, and that callback runs three phases in order:\n *\n * 1. MEASURE — every region builds its changeset. Nobody has run anything,\n * so nobody's in-flight values are polluting anybody else's measurement.\n * 2. MATCH — inserted ids are paired against removed ids in OTHER regions.\n * 3. RUN — every region compiles and plays its own timeline.\n *\n * All three are synchronous, so no frame is painted between a region measuring\n * and a region pinning its start values. That matters: the whole reason cues\n * pin synchronously is that one frame of the destination layout is visible\n * otherwise, and a barrier that yielded would hand that frame back.\n *\n * The pairing itself is the same shape as the counterpart matching a region\n * already does within itself. The receiving element is the one that flies: it\n * takes the sender's bounds as an `initial` it never had, and the sender is\n * dropped rather than orphaned, because the thing it would animate is already\n * being animated somewhere else.\n *\n * Bounds cross the boundary in PAGE space, which is the only space the two\n * regions agree on — a region-relative measurement means nothing to a region\n * somewhere else on the page.\n */\nimport { registerBusyProbe } from 'glimmer-motion';\n\nimport type { ChoreoNode, Sprite } from './types.ts';\n\n/** one region's measured pass, waiting for the others before it runs */\nexport interface Pass {\n claimed: ChoreoNode[];\n host: PassHost;\n inserted: Sprite[];\n kept: Sprite[];\n removed: Sprite[];\n root: HTMLElement;\n}\n\nexport interface PassHost {\n /** phase 3: compile and play, now that identities have been reconciled */\n finishPass(pass: Pass): void;\n /** phase 1: snapshot vs. now, into a changeset — no animation started yet */\n measurePass(): Pass | undefined;\n}\n\nconst intents = new Set<PassHost>();\nlet booked = false;\n/** a seam for the region to book the callback without far.ts importing the scheduler */\nlet schedule: (fn: () => void) => void = (fn) => fn();\n\nexport function setPassScheduler(fn: (run: () => void) => void) {\n schedule = fn;\n}\n\n/**\n * Announce that this region has a pass coming. Called while Glimmer is still\n * rendering, so by the time the barrier runs, every region taking part in this\n * pass is known — which is what lets the match be synchronous.\n */\nexport function join(host: PassHost) {\n intents.add(host);\n if (!booked) {\n booked = true;\n schedule(runBarrier);\n }\n}\n\nexport function leave(host: PassHost) {\n intents.delete(host);\n}\n\n/** a booked barrier is work outstanding: nothing has measured yet */\nregisterBusyProbe(() => booked && 'far-match barrier booked');\n\n/**\n * Drop a barrier that will never run. A test torn down between `join` and the\n * post-render callback leaves a destroyed region parked in `intents`, and the\n * next test's first pass would try to measure it. `setupChoreo(hooks)` runs\n * this before and after every test.\n */\nexport function resetBarrier() {\n intents.clear();\n booked = false;\n}\n\n/** mount everything rendered this pass before ANY region measures */\nlet beforeMeasure: () => void = () => {};\nexport function setBeforeMeasure(fn: () => void) {\n beforeMeasure = fn;\n}\n\nfunction runBarrier() {\n booked = false;\n const hosts = [...intents];\n intents.clear();\n beforeMeasure();\n const passes: Pass[] = [];\n for (const host of hosts) {\n const pass = host.measurePass();\n if (pass) {\n passes.push(pass);\n }\n }\n if (passes.length > 1) {\n match(passes);\n }\n for (const pass of passes) {\n pass.host.finishPass(pass);\n }\n}\n\nconst deltaOf = (s: Sprite) =>\n s.initial && s.final\n ? {\n height: s.final.page.height - s.initial.page.height,\n width: s.final.page.width - s.initial.page.width,\n x: s.final.page.x - s.initial.page.x,\n y: s.final.page.y - s.initial.page.y,\n }\n : undefined;\n\n/**\n * Pair an id inserted in one region against the same id removed in another.\n *\n * The receiver becomes a kept sprite carrying the sender as its counterpart —\n * exactly what same-region counterpart matching produces, so every step that\n * already understands `kept` and `counterpart` works across the boundary with\n * no further teaching. The sender is flagged `sent` so its own region knows to\n * let it go quietly instead of playing a removal for it.\n */\nfunction match(passes: Pass[]) {\n for (const pass of passes) {\n for (const s of pass.inserted) {\n if (s.id === null || s.counterpart) {\n continue;\n }\n for (const other of passes) {\n if (other === pass) {\n continue;\n }\n const sender = other.removed.find(\n (r) => r.id === s.id && !r.counterpart && !r.sent,\n );\n if (sender) {\n sender.sent = true;\n s.counterpart = sender;\n s.initial = sender.initial;\n s.type = 'kept';\n s.delta = deltaOf(s);\n break;\n }\n }\n }\n }\n}\n"],"names":["intents","Set","booked","schedule","fn","setPassScheduler","join","host","add","runBarrier","leave","delete","registerBusyProbe","resetBarrier","clear","beforeMeasure","setBeforeMeasure","hosts","passes","pass","measurePass","push","length","match","finishPass","deltaOf","s","initial","final","height","page","width","x","y","undefined","inserted","id","counterpart","other","sender","removed","find","r","sent","type","delta"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { array } from '@ember/helper';
|
|
2
|
+
import Component from '@glimmer/component';
|
|
3
|
+
import { motion } from 'glimmer-motion';
|
|
4
|
+
import { Choreo } from '../choreo.js';
|
|
5
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
6
|
+
import { setComponentTemplate } from '@ember/component';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A CLIP ON THE PICTURE. A video, a still or a freeze of the picture
|
|
10
|
+
* itself, standing over the frame for its window — full frame, or as an
|
|
11
|
+
* inset with its caption like the photograph. The media is not played by
|
|
12
|
+
* the browser's own clock: the film writes its source time every frame
|
|
13
|
+
* (a paused element seeked, a playing one corrected), so a clip is where
|
|
14
|
+
* the clock says it is whether the film played there or was scrubbed
|
|
15
|
+
* there. It arrives the way the insert does — wiped in — and leaves in a
|
|
16
|
+
* short fade.
|
|
17
|
+
*/
|
|
18
|
+
class Clip extends Component {
|
|
19
|
+
/** a pip is placed by the score, in percent of the frame */get place() {
|
|
20
|
+
const c = this.args.spec;
|
|
21
|
+
if (c.fit !== 'pip') {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
return [`left:${c.x ?? 66}%`, `top:${c.y ?? 10}%`, `width:${c.w ?? 30}%`, `border-radius:${c.radius ?? 0}%`, `--cf-pip-fade:${c.fade ?? 0.4}s`].join(';');
|
|
25
|
+
}
|
|
26
|
+
/** a pip fades; the editorial card is wiped in and out */
|
|
27
|
+
get isPip() {
|
|
28
|
+
return this.args.spec.fit === 'pip';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* THE LOOK THIS CLIP IS WEARING, as a CSS filter — composed in the same
|
|
32
|
+
* order the picture's own shader composes its grade (saturate, then
|
|
33
|
+
* contrast, then brightness, then sepia and hue), because filter order
|
|
34
|
+
* is not commutative and the two should agree about what a number
|
|
35
|
+
* means. Blur goes last, where a lens would put it.
|
|
36
|
+
*
|
|
37
|
+
* It rides the MEDIA, not the figure, so an inset's caption and its card
|
|
38
|
+
* are not graded with the footage.
|
|
39
|
+
*/
|
|
40
|
+
get look() {
|
|
41
|
+
const l = this.args.spec.look;
|
|
42
|
+
if (!l) {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
const f = [];
|
|
46
|
+
if (l.sat !== undefined) {
|
|
47
|
+
f.push(`saturate(${l.sat})`);
|
|
48
|
+
}
|
|
49
|
+
if (l.con !== undefined) {
|
|
50
|
+
f.push(`contrast(${l.con})`);
|
|
51
|
+
}
|
|
52
|
+
if (l.bri !== undefined) {
|
|
53
|
+
f.push(`brightness(${l.bri})`);
|
|
54
|
+
}
|
|
55
|
+
if (l.sepia !== undefined) {
|
|
56
|
+
f.push(`sepia(${l.sepia})`);
|
|
57
|
+
}
|
|
58
|
+
if (l.gray !== undefined) {
|
|
59
|
+
f.push(`grayscale(${l.gray})`);
|
|
60
|
+
}
|
|
61
|
+
if (l.hue !== undefined) {
|
|
62
|
+
f.push(`hue-rotate(${l.hue}deg)`);
|
|
63
|
+
}
|
|
64
|
+
if (l.blur !== undefined) {
|
|
65
|
+
f.push(`blur(${l.blur}px)`);
|
|
66
|
+
}
|
|
67
|
+
const out = f.length ? [`filter:${f.join(' ')}`] : [];
|
|
68
|
+
if (l.opacity !== undefined) {
|
|
69
|
+
out.push(`opacity:${l.opacity}`);
|
|
70
|
+
}
|
|
71
|
+
return out.join(';');
|
|
72
|
+
}
|
|
73
|
+
/** seconds a pip takes to arrive and to go */
|
|
74
|
+
get fade() {
|
|
75
|
+
return this.args.spec.fade ?? 0.4;
|
|
76
|
+
}
|
|
77
|
+
get muted() {
|
|
78
|
+
return !(this.args.spec.volume && this.args.spec.volume > 0);
|
|
79
|
+
}
|
|
80
|
+
get volume() {
|
|
81
|
+
return Math.max(0, Math.min(1, this.args.spec.volume ?? 0));
|
|
82
|
+
}
|
|
83
|
+
static {
|
|
84
|
+
setComponentTemplate(precompileTemplate("<Choreo @id=\"clip\" class=\"cf-clip is-{{if @spec.fit @spec.fit \"inset\"}} is-{{@state}}\" as |g|>\n {{#each (array @key) key=\"@identity\"}}\n <figure style={{this.place}} {{motion id=\"clip\" role=\"clip\"}}>\n {{#if (eq @spec.kind \"video\")}}\n <video src={{@src}} style={{this.look}} muted={{this.muted}} playsinline preload=\"auto\" {{@mount @lane}}></video>\n {{else if (eq @spec.kind \"image\")}}\n <img src={{@src}} style={{this.look}} alt {{@mount @lane}} />\n {{else}}\n {{#if @freeze}}\n <img src={{@freeze}} style={{this.look}} alt {{@mount @lane}} />\n {{/if}}\n {{/if}}\n {{#if @spec.caption}}\n <figcaption>\n <span class=\"cf-clip-cap\">{{@spec.caption}}</span>\n {{#if @spec.credit}}\n <span class=\"cf-clip-cr\">{{@spec.credit}}</span>\n {{/if}}\n </figcaption>\n {{/if}}\n </figure>\n {{/each}}\n {{#if this.isPip}}\n {{!-- A PIP FADES. It is a layer, not a photograph pinned to the\n page, so it arrives and leaves the way a layer does \u2014 on its own\n length, which the score sets. --}}\n <g.Tween @of={{g.inserted \"clip\"}} @opacity={{array 0 1}} @scale={{array 1.04 1}} @duration={{this.fade}} @ease=\"easeOut\" />\n <g.Tween @of={{g.removed \"clip\"}} @opacity={{0}} @duration={{this.fade}} @ease=\"easeIn\" />\n {{else}}\n <g.Tween @of={{g.inserted \"clip\"}} @clipPath={{array \"inset(0 100% 0 0)\" \"inset(0 0% 0 0)\"}} @duration={{0.62}} @ease={{array 0.22 1 0.36 1}} />\n <g.Tween @of={{g.removed \"clip\"}} @opacity={{0}} @duration={{0.24}} @ease=\"easeIn\" />\n {{/if}}\n</Choreo>", {
|
|
85
|
+
strictMode: true,
|
|
86
|
+
scope: () => ({
|
|
87
|
+
Choreo,
|
|
88
|
+
array,
|
|
89
|
+
motion,
|
|
90
|
+
eq
|
|
91
|
+
})
|
|
92
|
+
}), this);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function eq(a, b) {
|
|
96
|
+
return a === b;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { Clip };
|
|
100
|
+
//# sourceMappingURL=clip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip.js","sources":["../../src/film/clip.gts"],"sourcesContent":["import { array } from '@ember/helper';\nimport Component from '@glimmer/component';\nimport { motion } from 'glimmer-motion';\n\nimport { Choreo } from '../choreo.gts';\nimport type { ClipSpec, ClipState } from './clips.ts';\nimport type { ElementModifier } from './types.ts';\n\nexport interface ClipSignature {\n Args: {\n /** the picture, read back, when the clip is a freeze */\n freeze?: string;\n /** a new element per clip window, so the entrance replays */\n key: string;\n /** which lane this clip is on: the film's own handle is per lane */\n lane: number;\n /** the film's handle on the media element, for the per-frame sync */\n mount: ElementModifier<HTMLElement, [number]>;\n spec: ClipSpec;\n /** the resolved source, under `assets` */\n src: string;\n state: ClipState;\n };\n}\n\n/**\n * A CLIP ON THE PICTURE. A video, a still or a freeze of the picture\n * itself, standing over the frame for its window — full frame, or as an\n * inset with its caption like the photograph. The media is not played by\n * the browser's own clock: the film writes its source time every frame\n * (a paused element seeked, a playing one corrected), so a clip is where\n * the clock says it is whether the film played there or was scrubbed\n * there. It arrives the way the insert does — wiped in — and leaves in a\n * short fade.\n */\nexport class Clip extends Component<ClipSignature> {\n /** a pip is placed by the score, in percent of the frame */\n get place(): string {\n const c = this.args.spec;\n if (c.fit !== 'pip') {\n return '';\n }\n return [\n `left:${c.x ?? 66}%`,\n `top:${c.y ?? 10}%`,\n `width:${c.w ?? 30}%`,\n `border-radius:${c.radius ?? 0}%`,\n `--cf-pip-fade:${c.fade ?? 0.4}s`,\n ].join(';');\n }\n\n /** a pip fades; the editorial card is wiped in and out */\n get isPip(): boolean {\n return this.args.spec.fit === 'pip';\n }\n\n /**\n * THE LOOK THIS CLIP IS WEARING, as a CSS filter — composed in the same\n * order the picture's own shader composes its grade (saturate, then\n * contrast, then brightness, then sepia and hue), because filter order\n * is not commutative and the two should agree about what a number\n * means. Blur goes last, where a lens would put it.\n *\n * It rides the MEDIA, not the figure, so an inset's caption and its card\n * are not graded with the footage.\n */\n get look(): string {\n const l = this.args.spec.look;\n if (!l) {\n return '';\n }\n const f: string[] = [];\n if (l.sat !== undefined) {\n f.push(`saturate(${l.sat})`);\n }\n if (l.con !== undefined) {\n f.push(`contrast(${l.con})`);\n }\n if (l.bri !== undefined) {\n f.push(`brightness(${l.bri})`);\n }\n if (l.sepia !== undefined) {\n f.push(`sepia(${l.sepia})`);\n }\n if (l.gray !== undefined) {\n f.push(`grayscale(${l.gray})`);\n }\n if (l.hue !== undefined) {\n f.push(`hue-rotate(${l.hue}deg)`);\n }\n if (l.blur !== undefined) {\n f.push(`blur(${l.blur}px)`);\n }\n const out = f.length ? [`filter:${f.join(' ')}`] : [];\n if (l.opacity !== undefined) {\n out.push(`opacity:${l.opacity}`);\n }\n return out.join(';');\n }\n\n /** seconds a pip takes to arrive and to go */\n get fade(): number {\n return this.args.spec.fade ?? 0.4;\n }\n\n get muted(): boolean {\n return !(this.args.spec.volume && this.args.spec.volume > 0);\n }\n\n get volume(): number {\n return Math.max(0, Math.min(1, this.args.spec.volume ?? 0));\n }\n\n <template>\n <Choreo\n @id='clip'\n class='cf-clip is-{{if @spec.fit @spec.fit \"inset\"}} is-{{@state}}'\n as |g|\n >\n {{#each (array @key) key='@identity'}}\n <figure style={{this.place}} {{motion id='clip' role='clip'}}>\n {{#if (eq @spec.kind 'video')}}\n <video\n src={{@src}}\n style={{this.look}}\n muted={{this.muted}}\n playsinline\n preload='auto'\n {{@mount @lane}}\n ></video>\n {{else if (eq @spec.kind 'image')}}\n <img src={{@src}} style={{this.look}} alt='' {{@mount @lane}} />\n {{else}}\n {{#if @freeze}}\n <img\n src={{@freeze}}\n style={{this.look}}\n alt=''\n {{@mount @lane}}\n />\n {{/if}}\n {{/if}}\n {{#if @spec.caption}}\n <figcaption>\n <span class='cf-clip-cap'>{{@spec.caption}}</span>\n {{#if @spec.credit}}\n <span class='cf-clip-cr'>{{@spec.credit}}</span>\n {{/if}}\n </figcaption>\n {{/if}}\n </figure>\n {{/each}}\n {{#if this.isPip}}\n {{! A PIP FADES. It is a layer, not a photograph pinned to the\n page, so it arrives and leaves the way a layer does — on its own\n length, which the score sets. }}\n <g.Tween\n @of={{g.inserted 'clip'}}\n @opacity={{array 0 1}}\n @scale={{array 1.04 1}}\n @duration={{this.fade}}\n @ease='easeOut'\n />\n <g.Tween\n @of={{g.removed 'clip'}}\n @opacity={{0}}\n @duration={{this.fade}}\n @ease='easeIn'\n />\n {{else}}\n <g.Tween\n @of={{g.inserted 'clip'}}\n @clipPath={{array 'inset(0 100% 0 0)' 'inset(0 0% 0 0)'}}\n @duration={{0.62}}\n @ease={{array 0.22 1 0.36 1}}\n />\n <g.Tween\n @of={{g.removed 'clip'}}\n @opacity={{0}}\n @duration={{0.24}}\n @ease='easeIn'\n />\n {{/if}}\n </Choreo>\n </template>\n}\n\nfunction eq(a: unknown, b: unknown): boolean {\n return a === b;\n}\n"],"names":["Clip","Component","place","c","args","spec","fit","x","y","w","radius","fade","join","isPip","look","l","f","sat","undefined","push","con","bri","sepia","gray","hue","blur","out","length","opacity","muted","volume","Math","max","min","setComponentTemplate","precompileTemplate","strictMode","scope","Choreo","array","motion","eq","a","b"],"mappings":";;;;;;;AAyBA;;;;;;;;;;AAUO,MAAMA,IAAA,SAAaC,SAAA,CAAU;8DAElC,IAAIC,KAAAA,GAAgB;AAClB,IAAA,MAAMC,CAAA,GAAI,IAAI,CAACC,IAAI,CAACC,IAAI;AACxB,IAAA,IAAIF,CAAA,CAAEG,GAAG,KAAK,KAAA,EAAO;AACnB,MAAA,OAAO,EAAA;AACT,IAAA;IACA,OAAO,CACL,QAAQH,CAAA,CAAEI,CAAC,IAAI,EAAA,GAAK,EACpB,CAAA,IAAA,EAAOJ,CAAA,CAAEK,CAAC,IAAI,EAAA,CAAA,CAAA,CAAK,EACnB,CAAA,MAAA,EAASL,CAAA,CAAEM,CAAC,IAAI,EAAA,CAAA,CAAA,CAAK,EACrB,iBAAiBN,CAAA,CAAEO,MAAM,IAAI,CAAA,GAAI,EACjC,CAAA,cAAA,EAAiBP,CAAA,CAAEQ,IAAI,IAAI,GAAA,CAAA,CAAA,CAAM,CAClC,CAACC,IAAI,CAAC,GAAA,CAAA;AACT,EAAA;AAEA;EACA,IAAIC,KAAAA,GAAiB;IACnB,OAAO,IAAI,CAACT,IAAI,CAACC,IAAI,CAACC,GAAG,KAAK,KAAA;AAChC,EAAA;AAEA;;;;;;;;;;EAUA,IAAIQ,IAAAA,GAAe;IACjB,MAAMC,IAAI,IAAI,CAACX,IAAI,CAACC,IAAI,CAACS,IAAI;IAC7B,IAAI,CAACC,CAAA,EAAG;AACN,MAAA,OAAO,EAAA;AACT,IAAA;IACA,MAAMC,CAAS,GAAK,EAAE;AACtB,IAAA,IAAID,CAAA,CAAEE,GAAG,KAAKC,SAAA,EAAW;MACvBF,CAAA,CAAEG,IAAI,CAAC,CAAA,SAAA,EAAYJ,CAAA,CAAEE,GAAG,GAAG,CAAA;AAC7B,IAAA;AACA,IAAA,IAAIF,CAAA,CAAEK,GAAG,KAAKF,SAAA,EAAW;MACvBF,CAAA,CAAEG,IAAI,CAAC,CAAA,SAAA,EAAYJ,CAAA,CAAEK,GAAG,GAAG,CAAA;AAC7B,IAAA;AACA,IAAA,IAAIL,CAAA,CAAEM,GAAG,KAAKH,SAAA,EAAW;MACvBF,CAAA,CAAEG,IAAI,CAAC,CAAA,WAAA,EAAcJ,CAAA,CAAEM,GAAG,GAAG,CAAA;AAC/B,IAAA;AACA,IAAA,IAAIN,CAAA,CAAEO,KAAK,KAAKJ,SAAA,EAAW;MACzBF,CAAA,CAAEG,IAAI,CAAC,CAAA,MAAA,EAASJ,CAAA,CAAEO,KAAK,GAAG,CAAA;AAC5B,IAAA;AACA,IAAA,IAAIP,CAAA,CAAEQ,IAAI,KAAKL,SAAA,EAAW;MACxBF,CAAA,CAAEG,IAAI,CAAC,CAAA,UAAA,EAAaJ,CAAA,CAAEQ,IAAI,GAAG,CAAA;AAC/B,IAAA;AACA,IAAA,IAAIR,CAAA,CAAES,GAAG,KAAKN,SAAA,EAAW;MACvBF,CAAA,CAAEG,IAAI,CAAC,CAAA,WAAA,EAAcJ,CAAA,CAAES,GAAG,MAAM,CAAA;AAClC,IAAA;AACA,IAAA,IAAIT,CAAA,CAAEU,IAAI,KAAKP,SAAA,EAAW;MACxBF,CAAA,CAAEG,IAAI,CAAC,CAAA,KAAA,EAAQJ,CAAA,CAAEU,IAAI,KAAK,CAAA;AAC5B,IAAA;AACA,IAAA,MAAMC,GAAA,GAAMV,CAAA,CAAEW,MAAM,GAAG,CAAC,CAAA,OAAA,EAAUX,CAAA,CAAEJ,IAAI,CAAC,GAAA,CAAA,CAAA,CAAM,CAAC,GAAG,EAAE;AACrD,IAAA,IAAIG,CAAA,CAAEa,OAAO,KAAKV,SAAA,EAAW;MAC3BQ,GAAA,CAAIP,IAAI,CAAC,CAAA,QAAA,EAAWJ,CAAA,CAAEa,OAAO,EAAE,CAAA;AACjC,IAAA;AACA,IAAA,OAAOF,GAAA,CAAId,IAAI,CAAC,GAAA,CAAA;AAClB,EAAA;AAEA;EACA,IAAID,IAAAA,GAAe;IACjB,OAAO,IAAI,CAACP,IAAI,CAACC,IAAI,CAACM,IAAI,IAAI,GAAA;AAChC,EAAA;EAEA,IAAIkB,KAAAA,GAAiB;AACnB,IAAA,OAAO,EAAE,IAAI,CAACzB,IAAI,CAACC,IAAI,CAACyB,MAAM,IAAI,IAAI,CAAC1B,IAAI,CAACC,IAAI,CAACyB,MAAM,GAAG,CAAC,CAAA;AAC7D,EAAA;EAEA,IAAIA,MAAAA,GAAiB;IACnB,OAAOC,IAAA,CAAKC,GAAG,CAAC,CAAA,EAAGD,KAAKE,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC7B,IAAI,CAACC,IAAI,CAACyB,MAAM,IAAI,CAAA,CAAA,CAAA;AAC1D,EAAA;AAEA,EAAA;IAAAI,oBAAA,CAAAC,kBAAA,CAAA,uoDAAA,EAuEA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;QAAAC,MAAA;QAAAC,KAAA;QAAAC,MAAA;AAAAC,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;AAEA,SAASA,GAAGC,CAAU,EAAEC,CAAU,EAAU;EAC1C,OAAOD,CAAA,KAAMC,CAAA;AACf;;;;"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLIPS — a source-time window over the film's clock.
|
|
3
|
+
*
|
|
4
|
+
* A beat is a shot of the picture. A clip is something else laid over it
|
|
5
|
+
* for a while: a video, a still, or a freeze of the picture itself. Its
|
|
6
|
+
* arithmetic is the compositor's (notes/choreo-composition.md, Phase C3),
|
|
7
|
+
* lifted whole because it was already right:
|
|
8
|
+
*
|
|
9
|
+
* source = in + (film − start) × rate
|
|
10
|
+
* window = for, or (out − in) / rate, or the rest of the beat
|
|
11
|
+
*
|
|
12
|
+
* Before `start` the clip is absent; inside its window it is active and
|
|
13
|
+
* its source time is the number above; past it the end policy applies —
|
|
14
|
+
* `remove` (the default) takes it off, `hold` keeps the last sample on
|
|
15
|
+
* screen, `freeze` keeps it on screen and writes nothing further. Every
|
|
16
|
+
* state is re-derived from the film time on every frame, never discovered
|
|
17
|
+
* by playing from zero — which is what lets an exact film scrub into the
|
|
18
|
+
* middle of a clip and find it where it should be.
|
|
19
|
+
*
|
|
20
|
+
* A clip may outlive its beat: the window is measured on the film's
|
|
21
|
+
* clock, so a still that starts late in one shot can cross the seam into
|
|
22
|
+
* the next. That is the clip overlap an editor means.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A LOOK HELD ON A CLIP — the first adjustment that belongs to something
|
|
27
|
+
* other than the picture.
|
|
28
|
+
*
|
|
29
|
+
* `f.picture.Look` reaches the WebGL page's own grade; a clip is a DOM
|
|
30
|
+
* element and has never had one, so a video inset over a graded picture
|
|
31
|
+
* was ungraded and there was no way to say otherwise. These are the
|
|
32
|
+
* operations a browser can do to an element without a texture, composed
|
|
33
|
+
* in the order the film's own shader composes them (saturate, contrast,
|
|
34
|
+
* brightness, then sepia and hue) — because filter order is not
|
|
35
|
+
* commutative and the two should agree.
|
|
36
|
+
*
|
|
37
|
+
* What this is NOT is the whole filter stack: curves, secondaries and a
|
|
38
|
+
* LUT need the pixels, which needs the clip through a texture. This is
|
|
39
|
+
* the half a browser gives for free.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** how long a clip's window is, in film seconds */
|
|
43
|
+
function clipWindow(spec, beatStart, nextStart) {
|
|
44
|
+
if (spec.for !== undefined) {
|
|
45
|
+
return spec.for;
|
|
46
|
+
}
|
|
47
|
+
if (spec.out !== undefined) {
|
|
48
|
+
return (spec.out - (spec.in ?? 0)) / (spec.rate ?? 1);
|
|
49
|
+
}
|
|
50
|
+
/* the rest of the beat */
|
|
51
|
+
return Math.max(0, nextStart - beatStart - (spec.at ?? 0));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The clip on screen at a film time, if any: the most recent beat at or
|
|
56
|
+
* before `index` whose clip's window holds the time, or whose end policy
|
|
57
|
+
* keeps it on screen past its window. Older clips are never reached past
|
|
58
|
+
* a newer one — a clip that was removed is gone, and one that is held is
|
|
59
|
+
* the one on screen.
|
|
60
|
+
*/
|
|
61
|
+
function resolveClip(beats, beatStart, film, index, total, lane = 0) {
|
|
62
|
+
for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) {
|
|
63
|
+
const spec = laneOf(beats[i], lane);
|
|
64
|
+
if (!spec) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const start = beatStart(i) + (spec.at ?? 0);
|
|
68
|
+
if (film < start) {
|
|
69
|
+
/* not yet — only the current beat's own clip can be ahead of the
|
|
70
|
+
clock; an earlier beat's lies behind it by construction */
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const next = i + 1 < beats.length ? beatStart(i + 1) : total;
|
|
74
|
+
const window = clipWindow(spec, beatStart(i), next);
|
|
75
|
+
const since = film - start;
|
|
76
|
+
const rate = spec.rate ?? 1;
|
|
77
|
+
const inPoint = spec.in ?? 0;
|
|
78
|
+
if (since < window) {
|
|
79
|
+
return {
|
|
80
|
+
index: i,
|
|
81
|
+
since,
|
|
82
|
+
source: inPoint + since * rate,
|
|
83
|
+
spec,
|
|
84
|
+
state: 'active'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const end = spec.end ?? 'remove';
|
|
88
|
+
if (end === 'remove') {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
index: i,
|
|
93
|
+
since,
|
|
94
|
+
source: end === 'hold' ? inPoint + window * rate : null,
|
|
95
|
+
spec,
|
|
96
|
+
state: end === 'hold' ? 'held' : 'frozen'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** a beat's clip on one lane; a clip with no lane takes its own position */
|
|
103
|
+
function laneOf(beat, lane) {
|
|
104
|
+
const list = beat.clips;
|
|
105
|
+
if (!list?.length) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
109
|
+
if ((list[i].lane ?? i) === lane) {
|
|
110
|
+
return list[i];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** every lane any beat up to `index` puts a clip on, in order */
|
|
117
|
+
function clipLanes(beats, index) {
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) {
|
|
120
|
+
beats[i].clips?.forEach((spec, n) => seen.add(spec.lane ?? n));
|
|
121
|
+
}
|
|
122
|
+
return [...seen].sort((a, b) => a - b);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* EVERY CLIP ON SCREEN AT A FILM TIME, one per lane, in lane order — which
|
|
127
|
+
* is also paint order, so a score decides what sits over what by which
|
|
128
|
+
* lane it puts a thing on.
|
|
129
|
+
*
|
|
130
|
+
* A beat used to carry one clip and a second on the same shot overwrote
|
|
131
|
+
* the first without saying so. Lanes are the smallest thing that fixes
|
|
132
|
+
* that without inventing a track model: the resolver below is the old
|
|
133
|
+
* one, run once per lane.
|
|
134
|
+
*/
|
|
135
|
+
function resolveClips(beats, beatStart, film, index, total) {
|
|
136
|
+
const out = [];
|
|
137
|
+
for (const lane of clipLanes(beats, index)) {
|
|
138
|
+
const found = resolveClip(beats, beatStart, film, index, total, lane);
|
|
139
|
+
if (found) {
|
|
140
|
+
out.push(found);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { clipLanes, clipWindow, resolveClip, resolveClips };
|
|
147
|
+
//# sourceMappingURL=clips.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clips.js","sources":["../../src/film/clips.ts"],"sourcesContent":["/**\n * CLIPS — a source-time window over the film's clock.\n *\n * A beat is a shot of the picture. A clip is something else laid over it\n * for a while: a video, a still, or a freeze of the picture itself. Its\n * arithmetic is the compositor's (notes/choreo-composition.md, Phase C3),\n * lifted whole because it was already right:\n *\n * source = in + (film − start) × rate\n * window = for, or (out − in) / rate, or the rest of the beat\n *\n * Before `start` the clip is absent; inside its window it is active and\n * its source time is the number above; past it the end policy applies —\n * `remove` (the default) takes it off, `hold` keeps the last sample on\n * screen, `freeze` keeps it on screen and writes nothing further. Every\n * state is re-derived from the film time on every frame, never discovered\n * by playing from zero — which is what lets an exact film scrub into the\n * middle of a clip and find it where it should be.\n *\n * A clip may outlive its beat: the window is measured on the film's\n * clock, so a still that starts late in one shot can cross the seam into\n * the next. That is the clip overlap an editor means.\n */\nimport type { Beat } from './types.ts';\n\nexport type ClipKind = 'freeze' | 'image' | 'video';\n\nexport type ClipEnd = 'freeze' | 'hold' | 'remove';\n\nexport interface ClipSpec {\n /** seconds into the beat at which the clip appears (default 0) */\n at?: number;\n /** a line under an inset, and who to credit */\n caption?: string;\n credit?: string;\n /** past its window: taken off (default), held on its last sample, or frozen */\n end?: ClipEnd;\n /** a pip's own fade, seconds in and out (default 0.4) */\n fade?: number;\n /**\n * `cover` is the full frame over the picture; `inset` is the editorial\n * photograph, a paper card in the corner the type is not using; `pip`\n * is a LAYER — placed, sized and faded by the score, with nothing\n * around it. A pip is what a picture-in-picture actually is, and the\n * card is what an inset actually is; they were one thing and should\n * not have been.\n */\n fit?: 'cover' | 'inset' | 'pip';\n /** seconds it stays; default: to the end of the beat, or the source's own length */\n for?: number;\n /** the source's in point, seconds (video) */\n in?: number;\n kind: ClipKind;\n /**\n * WHICH LANE THIS CLIP IS ON, and it is the whole reason a beat can\n * carry more than one.\n *\n * Within a lane the rule is what it always was: the newest clip wins,\n * an older one is never reached past it, and an end policy decides\n * whether the lane empties or holds. Across lanes clips coexist, which\n * is what a picture-in-picture over a full-frame video needs and what a\n * single slot could not do — a second clip on a beat used to overwrite\n * the first silently.\n *\n * Defaults to the clip's own position among its beat's clips, so two\n * insets on one shot are two lanes without anybody saying so; name it\n * when a later beat should REPLACE an earlier clip rather than sit\n * beside it.\n */\n lane?: number;\n /** a look held on this clip alone (`f.clip.Look`) */\n look?: ClipLook;\n /** the source's out point, seconds (video); with `in`, sets the window */\n out?: number;\n /** a pip's corner radius, in percent of its width */\n radius?: number;\n /** source seconds per film second (video) */\n rate?: number;\n /** under `assets`; a freeze needs none */\n src?: string;\n /** the clip's own sound, 0..1; 0 (the default) keeps the film's voice in charge */\n volume?: number;\n /** a pip's width, percent of the frame (default 30) */\n w?: number;\n /** a pip's left edge, percent of the frame (default 66) */\n x?: number;\n /** a pip's top edge, percent of the frame (default 10) */\n y?: number;\n}\n\n/**\n * A LOOK HELD ON A CLIP — the first adjustment that belongs to something\n * other than the picture.\n *\n * `f.picture.Look` reaches the WebGL page's own grade; a clip is a DOM\n * element and has never had one, so a video inset over a graded picture\n * was ungraded and there was no way to say otherwise. These are the\n * operations a browser can do to an element without a texture, composed\n * in the order the film's own shader composes them (saturate, contrast,\n * brightness, then sepia and hue) — because filter order is not\n * commutative and the two should agree.\n *\n * What this is NOT is the whole filter stack: curves, secondaries and a\n * LUT need the pixels, which needs the clip through a texture. This is\n * the half a browser gives for free.\n */\nexport interface ClipLook {\n /** gaussian blur, pixels */\n blur?: number;\n /** exposure, 1 is unchanged */\n bri?: number;\n /** contrast, 1 is unchanged */\n con?: number;\n /** to grey, 0..1 */\n gray?: number;\n /** hue rotation, degrees */\n hue?: number;\n /** the whole layer's opacity, 0..1 */\n opacity?: number;\n /** saturation, 1 is unchanged, 0 is monochrome */\n sat?: number;\n /** to sepia, 0..1 */\n sepia?: number;\n}\n\nexport type ClipState = 'absent' | 'active' | 'frozen' | 'held';\n\nexport interface ResolvedClip {\n /** which beat's clip this is */\n index: number;\n /** film seconds since the clip's window opened */\n since: number;\n /** the source time to show, seconds; null when frozen */\n source: null | number;\n spec: ClipSpec;\n state: ClipState;\n}\n\n/** how long a clip's window is, in film seconds */\nexport function clipWindow(\n spec: ClipSpec,\n beatStart: number,\n nextStart: number,\n): number {\n if (spec.for !== undefined) {\n return spec.for;\n }\n if (spec.out !== undefined) {\n return (spec.out - (spec.in ?? 0)) / (spec.rate ?? 1);\n }\n /* the rest of the beat */\n return Math.max(0, nextStart - beatStart - (spec.at ?? 0));\n}\n\n/**\n * The clip on screen at a film time, if any: the most recent beat at or\n * before `index` whose clip's window holds the time, or whose end policy\n * keeps it on screen past its window. Older clips are never reached past\n * a newer one — a clip that was removed is gone, and one that is held is\n * the one on screen.\n */\nexport function resolveClip(\n beats: readonly Beat[],\n beatStart: (i: number) => number,\n film: number,\n index: number,\n total: number,\n lane = 0,\n): ResolvedClip | null {\n for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) {\n const spec = laneOf(beats[i]!, lane);\n if (!spec) {\n continue;\n }\n const start = beatStart(i) + (spec.at ?? 0);\n if (film < start) {\n /* not yet — only the current beat's own clip can be ahead of the\n clock; an earlier beat's lies behind it by construction */\n continue;\n }\n const next = i + 1 < beats.length ? beatStart(i + 1) : total;\n const window = clipWindow(spec, beatStart(i), next);\n const since = film - start;\n const rate = spec.rate ?? 1;\n const inPoint = spec.in ?? 0;\n if (since < window) {\n return {\n index: i,\n since,\n source: inPoint + since * rate,\n spec,\n state: 'active',\n };\n }\n const end = spec.end ?? 'remove';\n if (end === 'remove') {\n return null;\n }\n return {\n index: i,\n since,\n source: end === 'hold' ? inPoint + window * rate : null,\n spec,\n state: end === 'hold' ? 'held' : 'frozen',\n };\n }\n return null;\n}\n\n/** a beat's clip on one lane; a clip with no lane takes its own position */\nfunction laneOf(beat: Beat, lane: number): ClipSpec | undefined {\n const list = beat.clips;\n if (!list?.length) {\n return undefined;\n }\n for (let i = 0; i < list.length; i += 1) {\n if ((list[i]!.lane ?? i) === lane) {\n return list[i];\n }\n }\n return undefined;\n}\n\n/** every lane any beat up to `index` puts a clip on, in order */\nexport function clipLanes(beats: readonly Beat[], index: number): number[] {\n const seen = new Set<number>();\n for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) {\n beats[i]!.clips?.forEach((spec, n) => seen.add(spec.lane ?? n));\n }\n return [...seen].sort((a, b) => a - b);\n}\n\n/**\n * EVERY CLIP ON SCREEN AT A FILM TIME, one per lane, in lane order — which\n * is also paint order, so a score decides what sits over what by which\n * lane it puts a thing on.\n *\n * A beat used to carry one clip and a second on the same shot overwrote\n * the first without saying so. Lanes are the smallest thing that fixes\n * that without inventing a track model: the resolver below is the old\n * one, run once per lane.\n */\nexport function resolveClips(\n beats: readonly Beat[],\n beatStart: (i: number) => number,\n film: number,\n index: number,\n total: number,\n): ResolvedClip[] {\n const out: ResolvedClip[] = [];\n for (const lane of clipLanes(beats, index)) {\n const found = resolveClip(beats, beatStart, film, index, total, lane);\n if (found) {\n out.push(found);\n }\n }\n return out;\n}\n"],"names":["clipWindow","spec","beatStart","nextStart","for","undefined","out","in","rate","Math","max","at","resolveClip","beats","film","index","total","lane","i","min","length","laneOf","start","next","window","since","inPoint","source","state","end","beat","list","clips","clipLanes","seen","Set","forEach","n","add","sort","a","b","resolveClips","found","push"],"mappings":"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"}
|