@omnimedia/omnitool 1.1.0-84 → 1.1.0-86

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.
Files changed (41) hide show
  1. package/README.md +26 -26
  2. package/package.json +1 -1
  3. package/s/demo/routines/timeline-setup.ts +4 -5
  4. package/s/timeline/parts/animations/properties.ts +1 -5
  5. package/s/timeline/parts/animations/types.ts +14 -3
  6. package/s/timeline/parts/item.ts +3 -14
  7. package/s/timeline/renderers/parts/handy.ts +52 -11
  8. package/s/timeline/renderers/parts/samplers/visual/parts/sample.ts +2 -2
  9. package/s/timeline/sugar/helpers.ts +59 -48
  10. package/s/timeline/sugar/o.ts +79 -48
  11. package/s/timeline/types.ts +11 -3
  12. package/s/timeline/utils/anim.ts +0 -9
  13. package/x/demo/demo.bundle.min.js +100 -100
  14. package/x/demo/demo.bundle.min.js.map +3 -3
  15. package/x/demo/routines/timeline-setup.js +4 -3
  16. package/x/demo/routines/timeline-setup.js.map +1 -1
  17. package/x/index.html +2 -2
  18. package/x/tests.bundle.min.js +58 -58
  19. package/x/tests.bundle.min.js.map +4 -4
  20. package/x/tests.html +1 -1
  21. package/x/timeline/parts/animations/properties.d.ts +11 -13
  22. package/x/timeline/parts/animations/properties.js +1 -4
  23. package/x/timeline/parts/animations/properties.js.map +1 -1
  24. package/x/timeline/parts/animations/registry.d.ts +10 -10
  25. package/x/timeline/parts/animations/types.d.ts +12 -3
  26. package/x/timeline/parts/item.d.ts +8 -17
  27. package/x/timeline/parts/item.js +4 -5
  28. package/x/timeline/parts/item.js.map +1 -1
  29. package/x/timeline/renderers/parts/handy.js +42 -9
  30. package/x/timeline/renderers/parts/handy.js.map +1 -1
  31. package/x/timeline/renderers/parts/samplers/visual/parts/sample.js.map +1 -1
  32. package/x/timeline/sugar/helpers.d.ts +16 -9
  33. package/x/timeline/sugar/helpers.js +30 -27
  34. package/x/timeline/sugar/helpers.js.map +1 -1
  35. package/x/timeline/sugar/o.d.ts +7 -8
  36. package/x/timeline/sugar/o.js +52 -35
  37. package/x/timeline/sugar/o.js.map +1 -1
  38. package/x/timeline/types.d.ts +6 -3
  39. package/x/timeline/utils/anim.d.ts +0 -2
  40. package/x/timeline/utils/anim.js +0 -4
  41. package/x/timeline/utils/anim.js.map +1 -1
package/README.md CHANGED
@@ -146,22 +146,24 @@ const timeline = omni.timeline(o => {
146
146
  })
147
147
  ```
148
148
 
149
- Animated spatial transforms:
149
+ Animations:
150
150
 
151
151
  ```ts
152
152
  const timeline = omni.timeline(o => {
153
- const customMotion = o.animatedSpatial(
154
- o.anim.transform("linear", [
155
- [0, o.transform({position: [-400, 0]})],
156
- [1000, o.transform({position: [0, 0]})],
157
- ])
158
- )
153
+ const fadeIn = o.animate.opacity.make("easeIn", [
154
+ [0, 0],
155
+ [700, 1],
156
+ ])
157
+ const slideOut = o.animate.transform.make("linear", [
158
+ [0, o.transform({position: [0, 0]})],
159
+ [1000, o.transform({position: [400, 0]})],
160
+ ])
159
161
 
160
162
  const title = o.text("Lower third", {
161
163
  duration: 2000,
162
164
  styles: {fill: "white", fontSize: 36}
163
165
  })
164
- o.set(title.id, {spatialId: customMotion.id})
166
+ o.set(title.id, {animationIds: [fadeIn.id, slideOut.id]})
165
167
 
166
168
  return o.stack(
167
169
  o.video(clip, {duration: 4000}),
@@ -170,31 +172,28 @@ const timeline = omni.timeline(o => {
170
172
  })
171
173
  ```
172
174
 
173
- Built-in spatial animations:
175
+ Built-in transform animations:
174
176
 
175
177
  ```ts
176
178
  const animOut = {
177
179
  duration: 500,
178
180
  offset: item.duration - 500,
179
181
  }
180
- const slideIn = o.animatedSpatial(o.anim.presets.slideIn())
181
- const slideOut = o.animatedSpatial(o.anim.presets.slideOut(animOut))
182
- const spinIn = o.animatedSpatial(o.anim.presets.spinIn())
183
- const spinOut = o.animatedSpatial(o.anim.presets.spinOut(animOut))
184
- const zoomIn = o.animatedSpatial(o.anim.presets.zoomIn())
185
- const zoomOut = o.animatedSpatial(o.anim.presets.zoomOut(animOut))
186
- const bounceIn = o.animatedSpatial(o.anim.presets.bounceIn())
187
- const bounceOut = o.animatedSpatial(o.anim.presets.bounceOut(animOut))
182
+ const slideIn = o.animate.presets.slideIn.make()
183
+ const slideOut = o.animate.presets.slideOut.make(animOut)
184
+ const spinIn = o.animate.presets.spinIn.make()
185
+ const spinOut = o.animate.presets.spinOut.make(animOut)
186
+ const zoomIn = o.animate.presets.zoomIn.make()
187
+ const zoomOut = o.animate.presets.zoomOut.make(animOut)
188
+ const bounceIn = o.animate.presets.bounceIn.make()
189
+ const bounceOut = o.animate.presets.bounceOut.make(animOut)
188
190
  ```
189
191
 
190
192
  Built-in scalar animations:
191
193
 
192
194
  ```ts
193
- const fadeInPreset = o.anim.presets.fadeIn()
194
- const fadeIn = o.animate.opacity.make(fadeInPreset.terp, fadeInPreset.track)
195
-
196
- const fadeOutPreset = o.anim.presets.fadeOut({duration: 500})
197
- const fadeOut = o.animate.opacity.make(fadeOutPreset.terp, fadeOutPreset.track)
195
+ const fadeIn = o.animate.presets.fadeIn.make()
196
+ const fadeOut = o.animate.presets.fadeOut.make(animOut)
198
197
  ```
199
198
 
200
199
  Animation application:
@@ -233,7 +232,7 @@ const timeline = omni.timeline(o => {
233
232
  duration: 2000,
234
233
  styles: {fill: "white", fontSize: 36},
235
234
  })
236
- o.set(title.id, {animationId: fadeIn.id})
235
+ o.set(title.id, {animationIds: [fadeIn.id]})
237
236
 
238
237
  return o.stack(
239
238
  o.video(clip, {duration: 4000}),
@@ -269,8 +268,9 @@ Object.entries(animationPresets).forEach(([preset, meta]) => {
269
268
  ```
270
269
 
271
270
  Animatable properties describe what can be keyframed, such as `transform` and `opacity`.
272
- Animation presets describe built-in recipes, such as `slideIn` and `fadeIn`, that create animation data.
273
- Use `animationPresets` to list available recipes, and `o.anim.presets` to create animation data from them.
271
+ Animation presets describe built-in recipes, such as `slideIn` and `fadeIn`.
272
+ Use `animationPresets` to list available recipes, and `o.animate.presets` to create animation items.
273
+ Use `o.animate` to create or apply animation items.
274
274
 
275
275
  Preset options:
276
276
  - `duration` sets the animation duration, defaulting to `700`.
@@ -285,7 +285,7 @@ Utils:
285
285
  ```ts
286
286
  import {resolveScalarAnimation, resolveTransformAnimation} from "@omnimedia/omnitool"
287
287
 
288
- const transform = resolveTransformAnimation(localTime, spatial.anim)
288
+ const transform = resolveTransformAnimation(localTime, transformAnimation)
289
289
  const opacity = resolveScalarAnimation(localTime, opacityAnimation)
290
290
  ```
291
291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnimedia/omnitool",
3
- "version": "1.1.0-84",
3
+ "version": "1.1.0-86",
4
4
  "description": "open source video processing tools",
5
5
  "license": "MIT",
6
6
  "author": "Przemysław Gałęzki",
@@ -22,17 +22,16 @@ export async function TimelineSchemaTest(driver: Driver, file: File) {
22
22
  }),
23
23
  [0.15, 0.1, 0.05, 0.2],
24
24
  )
25
- const textSpatial = o.animatedSpatial(
26
- o.anim.transform("linear", [
25
+ const textSpatial = o.spatial()
26
+ const textMotion = o.animate.transform.make("linear", [
27
27
  [0, o.transform({position: [-320, 80], scale: [0.7, 0.7]})],
28
28
  [1000, o.transform({position: [120, 80], scale: [1, 1]})],
29
29
  [2000, o.transform({position: [200, 40], scale: [1.35, 1.35], rotation: 8})],
30
30
  [3000, o.transform({position: [320, 0], scale: [1.15, 1.15], rotation: 0})],
31
- ]),
32
- )
31
+ ])
33
32
 
34
33
  const video = o.video(videoA, {duration: 3000, start: 1000})
35
- o.set<Item.Text>(text.id, {styleId: style.id, spatialId: textSpatial.id, animationId: fade.id})
34
+ o.set<Item.Text>(text.id, {styleId: style.id, spatialId: textSpatial.id, animationIds: [fade.id, textMotion.id]})
36
35
  o.set<Item.Video>(video.id, {spatialId: videoSpatial.id})
37
36
 
38
37
  return o.sequence(
@@ -1,7 +1,7 @@
1
1
 
2
2
  import type {AnimationDefinition} from "./types.js"
3
3
 
4
- export const spatialAnimations = {
4
+ export const visualAnimations = {
5
5
  transform: {
6
6
  type: "transform",
7
7
  defaultTerp: "linear",
@@ -13,9 +13,6 @@ export const spatialAnimations = {
13
13
  {path: "rotation", type: "number", default: 0, unit: "radian"},
14
14
  ],
15
15
  },
16
- } as const satisfies Record<string, AnimationDefinition>
17
-
18
- export const visualAnimations = {
19
16
  opacity: {
20
17
  type: "scalar",
21
18
  defaultTerp: "linear",
@@ -28,7 +25,6 @@ export const visualAnimations = {
28
25
  // const audioAnimations = {}
29
26
 
30
27
  export const animatableProperties = {
31
- ...spatialAnimations,
32
28
  ...visualAnimations,
33
29
  } as const
34
30
 
@@ -1,7 +1,8 @@
1
1
 
2
2
  import {animationPresets} from "./presets.js"
3
- import {spatialAnimations, visualAnimations, animatableProperties} from "./properties.js"
4
- import {Anim, Interpolation, Keyframes, TrackTransform, TransformOptions, Vec2} from "../../types.js"
3
+ import type {Item, VisualAnimatableItem} from "../item.js"
4
+ import {visualAnimations, animatableProperties} from "./properties.js"
5
+ import type {Anim, Interpolation, Keyframes, ScalarAnimation, TrackTransform, TransformAnimation, TransformOptions, Vec2} from "../../types.js"
5
6
 
6
7
  export type AnimationType = "scalar" | "transform"
7
8
  export type AnimationChannelType = "number"
@@ -41,7 +42,6 @@ export type AnimationPresetDefinition =
41
42
  | MotionAnimationPresetDefinition
42
43
  | ScalarAnimationPresetDefinition
43
44
 
44
- export type SpatialAnimationProperty = keyof typeof spatialAnimations
45
45
  export type VisualAnimationProperty = keyof typeof visualAnimations
46
46
  export type AnimationProperty = keyof typeof animatableProperties
47
47
  export type AnimationPreset = keyof typeof animationPresets
@@ -67,3 +67,14 @@ export type AnimationPresetOptions<Value> = {
67
67
 
68
68
  export type MotionAnimationOptions = AnimationPresetOptions<Vec2>
69
69
  export type ScalarAnimationOptions = AnimationPresetOptions<number>
70
+ export type PresetAnimation = ScalarAnimation | TransformAnimation
71
+ export type PresetOptions = MotionAnimationOptions | ScalarAnimationOptions
72
+
73
+ export interface PresetAnimateAction {
74
+ <T extends VisualAnimatableItem>(item: T, options?: PresetOptions): T
75
+ make(options?: PresetOptions): Item.Animation
76
+ }
77
+
78
+ export type PresetAnimateActions = {
79
+ [TKey in AnimationPreset]: PresetAnimateAction
80
+ }
@@ -4,7 +4,7 @@ import {TextStyleOptions} from "pixi.js"
4
4
  import {Id, Hash} from "./basics.js"
5
5
  import {Ms} from "../../units/ms.js"
6
6
  import type {FilterParams, FilterType} from "./filters.js"
7
- import {Anim, TrackTransform, Transform, VisualAnimations} from "../types.js"
7
+ import {Transform, VisualAnimations} from "../types.js"
8
8
 
9
9
  export type Crop = [top: number, right: number, bottom: number, left: number]
10
10
 
@@ -16,7 +16,6 @@ export enum Kind {
16
16
  Text,
17
17
  Gap,
18
18
  Spatial,
19
- AnimatedSpatial,
20
19
  Animation,
21
20
  Transition,
22
21
  TextStyle,
@@ -42,14 +41,6 @@ export namespace Item {
42
41
  enabled: boolean
43
42
  }
44
43
 
45
- export type AnimatedSpatial = {
46
- id: Id
47
- kind: Kind.AnimatedSpatial
48
- anim: Anim<TrackTransform>
49
- crop?: Crop
50
- enabled: boolean
51
- }
52
-
53
44
  export type Animation = {
54
45
  id: Id
55
46
  kind: Kind.Animation
@@ -94,7 +85,7 @@ export namespace Item {
94
85
  start: number
95
86
  duration: number
96
87
  spatialId?: Id
97
- animationId?: Id
88
+ animationIds?: Id[]
98
89
  filterIds?: Id[]
99
90
  }
100
91
 
@@ -113,7 +104,7 @@ export namespace Item {
113
104
  content: string
114
105
  duration: number
115
106
  spatialId?: Id
116
- animationId?: Id
107
+ animationIds?: Id[]
117
108
  styleId?: Id
118
109
  filterIds?: Id[]
119
110
  }
@@ -134,7 +125,6 @@ export namespace Item {
134
125
  | Gap
135
126
  | Transition
136
127
  | Spatial
137
- | AnimatedSpatial
138
128
  | Animation
139
129
  | TextStyle
140
130
  | Filter
@@ -144,7 +134,6 @@ export namespace Item {
144
134
  export type ContainerItem = Item.Sequence | Item.Stack
145
135
  export type NonContainerItem = Exclude<Item.Any, ContainerItem>
146
136
  export type FilterableItem = Item.Sequence | Item.Stack | Item.Video | Item.Text
147
- export type SpatialItem = Item.Spatial | Item.AnimatedSpatial
148
137
  export type VisualAnimatableItem = Item.Video | Item.Text
149
138
 
150
139
  export type PlayableItem = Item.Any & {
@@ -1,10 +1,11 @@
1
1
 
2
2
  import {ms, Ms} from '../../../units/ms.js'
3
3
  import {Id, TimelineFile} from '../../parts/basics.js'
4
+ import {Keyframes, TransformAnimation} from '../../types.js'
4
5
  import { SampleContext } from './samplers/visual/parts/types.js'
5
6
  import {I6, Mat6, mul6, transformToMat6} from '../../utils/matrix.js'
6
- import {resolveScalarAnimation, resolveTransform} from '../../utils/anim.js'
7
- import {ContainerItem, Item, Kind, PlayableItem, SpatialItem} from '../../parts/item.js'
7
+ import {ContainerItem, Item, Kind, PlayableItem} from '../../parts/item.js'
8
+ import {resolveScalarAnimation, resolveTransformAnimation} from '../../utils/anim.js'
8
9
 
9
10
  function isPlayableItem(item: Item.Any): item is PlayableItem {
10
11
  return 'duration' in item
@@ -98,14 +99,27 @@ function applySpatialIfAny(
98
99
  parentMatrix: Mat6,
99
100
  time: Ms,
100
101
  ) {
102
+ let matrix = parentMatrix
101
103
  if ("spatialId" in item && item.spatialId) {
102
- const spatial = items.get(item.spatialId) as SpatialItem | undefined
104
+ const spatial = items.get(item.spatialId) as Item.Spatial | undefined
103
105
  if (spatial?.enabled) {
104
- const local = transformToMat6(resolveTransform(spatial, time))
105
- return mul6(local, parentMatrix)
106
+ const local = transformToMat6(spatial.transform)
107
+ matrix = mul6(local, matrix)
106
108
  }
107
109
  }
108
- return parentMatrix
110
+
111
+ if ("animationIds" in item && item.animationIds) {
112
+ for (const id of item.animationIds) {
113
+ const animation = items.get(id) as Item.Animation | undefined
114
+ const anim = animation?.anims.transform
115
+ if (animation?.enabled && anim && transformActiveAt(anim, time)) {
116
+ const local = transformToMat6(resolveTransformAnimation(time, anim))
117
+ matrix = mul6(local, matrix)
118
+ }
119
+ }
120
+ }
121
+
122
+ return matrix
109
123
  }
110
124
 
111
125
  export function walk(
@@ -366,12 +380,39 @@ export function computeOpacity(
366
380
  item: Item.Any,
367
381
  time: Ms,
368
382
  ) {
369
- if (!("animationId" in item) || item.animationId === undefined)
383
+ if (!("animationIds" in item) || item.animationIds === undefined)
370
384
  return 1
371
385
 
372
- const animation = ctx.items.get(item.animationId) as Item.Animation | undefined
373
- return animation?.enabled && animation.anims.opacity
374
- ? resolveScalarAnimation(time, animation.anims.opacity)
375
- : 1
386
+ let opacity = 1
387
+ for (const id of item.animationIds) {
388
+ const animation = ctx.items.get(id) as Item.Animation | undefined
389
+ const anim = animation?.anims.opacity
390
+ if (animation?.enabled && anim && keyframesActiveAt(anim.track, time))
391
+ opacity = resolveScalarAnimation(time, anim)
392
+ }
393
+ return opacity
394
+ }
395
+
396
+ function keyframesActiveAt(keys: Keyframes, time: Ms) {
397
+ if (keys.length === 0)
398
+ return false
399
+
400
+ let start = keys[0][0]
401
+ let end = keys[0][0]
402
+ for (const [keyTime] of keys) {
403
+ start = Math.min(start, keyTime)
404
+ end = Math.max(end, keyTime)
405
+ }
406
+
407
+ return time >= start && time <= end
376
408
  }
377
409
 
410
+ function transformActiveAt(anim: TransformAnimation, time: Ms) {
411
+ return (
412
+ keyframesActiveAt(anim.track.position.x, time) ||
413
+ keyframesActiveAt(anim.track.position.y, time) ||
414
+ keyframesActiveAt(anim.track.scale.x, time) ||
415
+ keyframesActiveAt(anim.track.scale.y, time) ||
416
+ keyframesActiveAt(anim.track.rotation, time)
417
+ )
418
+ }
@@ -2,7 +2,7 @@
2
2
  import {SampleContext} from "./types.js"
3
3
  import {sampleSequence} from "./sequence.js"
4
4
  import {Ms} from "../../../../../../units/ms.js"
5
- import {Item, Kind, SpatialItem} from "../../../../../parts/item.js"
5
+ import {Item, Kind} from "../../../../../parts/item.js"
6
6
  import {FilterSpec, Layer} from "../../../../../../driver/fns/schematic.js"
7
7
  import {AncestorAt, computeOpacity, computeWorldMatrix} from "../../../handy.js"
8
8
 
@@ -15,7 +15,7 @@ export async function sampleVisual(
15
15
  const matrix = computeWorldMatrix(ctx.items, ancestors, item, time)
16
16
  const alpha = computeOpacity(ctx, item, time)
17
17
  const crop = "spatialId" in item && item.spatialId
18
- ? (ctx.items.get(item.spatialId) as SpatialItem | undefined)?.crop
18
+ ? (ctx.items.get(item.spatialId) as Item.Spatial | undefined)?.crop
19
19
  : undefined
20
20
  const filters = "filterIds" in item && item.filterIds
21
21
  ? item.filterIds
@@ -6,37 +6,17 @@ import {Media} from "../parts/media.js"
6
6
  import {TimelineFile} from "../parts/basics.js"
7
7
  import {FilterAction} from "../parts/filters.js"
8
8
  import {filters, FilterParams, FilterType} from "../parts/filters.js"
9
+ import {AnimationPreset, PresetOptions} from "../parts/animations/types.js"
9
10
  import {Crop, FilterableItem, Item, VisualAnimatableItem} from "../parts/item.js"
10
- import {makeAnimationPresets, visualAnimations} from "../parts/animations/registry.js"
11
- import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, TransformOptions, Vec2, VisualAnimations} from "../types.js"
12
-
13
- const transformFrom = (options: TransformOptions): Transform => [
14
- options.position ?? [0, 0],
15
- options.scale ?? [1, 1],
16
- options.rotation ?? 0,
17
- ]
18
-
19
- const transformAnimation = (terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> => {
20
- const track: TrackTransform = {
21
- position: {x: [], y: []},
22
- scale: {x: [], y: []},
23
- rotation: [],
24
- }
25
-
26
- for (const [time, [position, scale, rotation]] of source) {
27
- track.position.x.push([time, position[0]])
28
- track.position.y.push([time, position[1]])
29
- track.scale.x.push([time, scale[0]])
30
- track.scale.y.push([time, scale[1]])
31
- track.rotation.push([time, rotation])
32
- }
33
-
34
- return {terp, track}
35
- }
11
+ import {animationPresets, visualAnimations} from "../parts/animations/registry.js"
12
+ import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, Vec2, VisualAnimationInput, VisualAnimations} from "../types.js"
36
13
 
37
14
  export type Build<T extends Item.Any = Item.Any> = (o: O) => T
38
15
  type BuildVisualAnimateActions = {
39
- [TKey in keyof VisualAnimations]-?: BuildAnimateAction
16
+ [TKey in keyof VisualAnimations]-?: BuildAnimateAction<TKey>
17
+ }
18
+ type BuildPresetAnimateActions = {
19
+ [TKey in AnimationPreset]: BuildPresetAnimateAction
40
20
  }
41
21
 
42
22
  function createTimeline(): TimelineFile {
@@ -103,10 +83,6 @@ export function spatial(transform?: Transform, crop?: Crop): Build<Item.Spatial>
103
83
  return o => o.spatial(transform, crop)
104
84
  }
105
85
 
106
- export function animatedSpatial(anim: Anim<TrackTransform>, crop?: Crop): Build<Item.AnimatedSpatial> {
107
- return o => o.animatedSpatial(anim, crop)
108
- }
109
-
110
86
  export const anim = {
111
87
  scalar(terp: Interpolation, track: Keyframes): Anim<Keyframes> {
112
88
  return {terp, track}
@@ -123,13 +99,23 @@ export const anim = {
123
99
  return {terp, track}
124
100
  },
125
101
 
126
- transform: transformAnimation,
102
+ transform: (terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> => {
103
+ const track: TrackTransform = {
104
+ position: {x: [], y: []},
105
+ scale: {x: [], y: []},
106
+ rotation: [],
107
+ }
108
+
109
+ for (const [time, [position, scale, rotation]] of source) {
110
+ track.position.x.push([time, position[0]])
111
+ track.position.y.push([time, position[1]])
112
+ track.scale.x.push([time, scale[0]])
113
+ track.scale.y.push([time, scale[1]])
114
+ track.rotation.push([time, rotation])
115
+ }
127
116
 
128
- presets: makeAnimationPresets(
129
- (terp, track) => ({terp, track}),
130
- transformAnimation,
131
- transformFrom,
132
- ),
117
+ return {terp, track}
118
+ }
133
119
  }
134
120
 
135
121
  interface BuildFilterAction<TFilter extends FilterType> {
@@ -137,13 +123,18 @@ interface BuildFilterAction<TFilter extends FilterType> {
137
123
  make(params?: FilterParams<TFilter>): Build<Item.Filter<TFilter>>
138
124
  }
139
125
 
140
- interface BuildAnimateAction {
126
+ interface BuildAnimateAction<TKey extends keyof VisualAnimations = keyof VisualAnimations> {
141
127
  <T extends VisualAnimatableItem>(
142
128
  item: Build<T>,
143
129
  terp: Interpolation,
144
- track: Keyframes
130
+ track: VisualAnimationInput<TKey>
145
131
  ): Build<T>
146
- make(terp: Interpolation, track: Keyframes): Build<Item.Animation>
132
+ make(terp: Interpolation, track: VisualAnimationInput<TKey>): Build<Item.Animation>
133
+ }
134
+
135
+ interface BuildPresetAnimateAction {
136
+ <T extends VisualAnimatableItem>(item: Build<T>, options?: PresetOptions): Build<T>
137
+ make(options?: PresetOptions): Build<Item.Animation>
147
138
  }
148
139
 
149
140
  type BuildFilterActions = {
@@ -172,25 +163,45 @@ function makeFilters(): BuildFilterActions {
172
163
 
173
164
  export const filter = makeFilters()
174
165
 
175
- function makeAnimate(
176
- get: (o: O) => AnimateAction
177
- ): BuildAnimateAction {
166
+ function makeAnimate<TKey extends keyof VisualAnimations>(
167
+ get: (o: O) => AnimateAction<TKey>
168
+ ): BuildAnimateAction<TKey> {
178
169
  const action = (<T extends VisualAnimatableItem>(
179
170
  item: Build<T>,
180
171
  terp: Interpolation,
181
- track: Keyframes
182
- ): Build<T> => o => get(o)(item(o), terp, track)) as BuildAnimateAction
183
- action.make = (terp: Interpolation, track: Keyframes) => o => get(o).make(terp, track)
172
+ track: VisualAnimationInput<TKey>
173
+ ): Build<T> => o => get(o)(item(o), terp, track)) as BuildAnimateAction<TKey>
174
+ action.make = (terp: Interpolation, track: VisualAnimationInput<TKey>) => o => get(o).make(terp, track)
184
175
  return action
185
176
  }
186
177
 
187
178
  function makeAnimateActions(): BuildVisualAnimateActions {
188
179
  const entries = Object.keys(visualAnimations)
189
- .map(key => [key, makeAnimate(o => o.animate[key as keyof VisualAnimations] as AnimateAction)])
180
+ .map(key => [key, makeAnimate(o => o.animate[key as keyof VisualAnimations] as AnimateAction<any>)])
190
181
  return Object.fromEntries(entries) as BuildVisualAnimateActions
191
182
  }
192
183
 
193
- export const animate = makeAnimateActions()
184
+ function makePresetAnimate(
185
+ key: AnimationPreset
186
+ ): BuildPresetAnimateAction {
187
+ const action = (<T extends VisualAnimatableItem>(
188
+ item: Build<T>,
189
+ options?: PresetOptions
190
+ ): Build<T> => o => o.animate.presets[key](item(o), options)) as BuildPresetAnimateAction
191
+ action.make = (options?: PresetOptions) => o => o.animate.presets[key].make(options)
192
+ return action
193
+ }
194
+
195
+ function makePresetAnimateActions(): BuildPresetAnimateActions {
196
+ const entries = Object.keys(animationPresets)
197
+ .map(key => [key, makePresetAnimate(key as AnimationPreset)])
198
+ return Object.fromEntries(entries) as BuildPresetAnimateActions
199
+ }
200
+
201
+ export const animate = {
202
+ ...makeAnimateActions(),
203
+ presets: makePresetAnimateActions(),
204
+ }
194
205
 
195
206
  export function textStyle(style: TextStyleOptions): Build<Item.TextStyle> {
196
207
  return o => o.textStyle(style)