@px-petals/roku.motion-web 0.0.0-stage → 1.0.0
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/LICENSE +201 -0
- package/audio/audio.cues.js +27 -0
- package/audio/audio.js +259 -0
- package/bands/bands.js +390 -0
- package/bundle/bundle.fonts.js +55 -0
- package/bundle/bundle.js +75 -0
- package/focus/focus.direction.js +46 -0
- package/focus/focus.dom.js +158 -0
- package/focus/focus.js +277 -0
- package/focus/focus.tree.js +146 -0
- package/hit/hit.geometry.js +113 -0
- package/hit/hit.js +129 -0
- package/list/list.flex.js +181 -0
- package/list/list.items.js +80 -0
- package/list/list.js +507 -0
- package/lottie/lottie.js +124 -0
- package/lottie/lottie.layers.js +363 -0
- package/lottie/lottie.paint.js +208 -0
- package/lottie/lottie.paths.js +459 -0
- package/lottie/lottie.props.js +255 -0
- package/lottie/lottie.shapes.js +258 -0
- package/mixer/mixer.js +284 -0
- package/mixer/mixer.mix.js +90 -0
- package/mixer/mixer.stage.js +431 -0
- package/motion/motion.convert.js +373 -0
- package/motion/motion.flow.js +403 -0
- package/motion/motion.keys.js +150 -0
- package/motion/motion.machine.js +367 -0
- package/motion/motion.nested.js +345 -0
- package/motion/motion.values.js +364 -0
- package/motion.js +979 -0
- package/package.json +53 -4
- package/player/overlay/overlay.js +325 -0
- package/player/overlay/overlay.layout.js +208 -0
- package/player/player.fit.js +101 -0
- package/player/player.js +401 -0
- package/player/player.raster.js +127 -0
- package/pointer/pointer.js +292 -0
- package/readme.md +270 -0
- package/README.md +0 -3
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data converters: the declarative descriptors a bundle's `data.slots` carry
|
|
3
|
+
* on a source (rive2lottie writes them, dotlottie.converters.js lists them),
|
|
4
|
+
* applied to a view-model value on its way to a slot, and reversed for a bind
|
|
5
|
+
* that writes back. A descriptor type this code does not know warns once and
|
|
6
|
+
* passes the value through.
|
|
7
|
+
*
|
|
8
|
+
* The same arithmetic runs at build time for the default instance
|
|
9
|
+
* (dotlottie.convert.js in the converter) and on Roku (bundle.convert.bs).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ConvertContext
|
|
14
|
+
* @property {(path: string) => any} [read] a view-model property's value, by dotted path
|
|
15
|
+
* @property {string[]} [names] the names of an enum the value indexes
|
|
16
|
+
* @property {(message: string) => void} [warn]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Operations of an `operation` descriptor and a formula, by name.
|
|
21
|
+
* @type {Record<string, (a: number, b: number) => number>}
|
|
22
|
+
*/
|
|
23
|
+
const OPERATIONS = {
|
|
24
|
+
add: (a, b) => a + b,
|
|
25
|
+
sub: (a, b) => a - b,
|
|
26
|
+
mul: (a, b) => a * b,
|
|
27
|
+
div: (a, b) => (b === 0 ? 0 : a / b),
|
|
28
|
+
mod: (a, b) => (b === 0 ? 0 : a % b),
|
|
29
|
+
sqrt: (a) => Math.sqrt(a),
|
|
30
|
+
pow: (a, b) => Math.pow(a, b),
|
|
31
|
+
exp: (a) => Math.exp(a),
|
|
32
|
+
log: (a) => Math.log(a),
|
|
33
|
+
cos: (a) => Math.cos(a),
|
|
34
|
+
sin: (a) => Math.sin(a),
|
|
35
|
+
tan: (a) => Math.tan(a),
|
|
36
|
+
acos: (a) => Math.acos(a),
|
|
37
|
+
asin: (a) => Math.asin(a),
|
|
38
|
+
atan: (a) => Math.atan(a),
|
|
39
|
+
atan2: (a, b) => Math.atan2(a, b),
|
|
40
|
+
round: (a) => Math.round(a),
|
|
41
|
+
floor: (a) => Math.floor(a),
|
|
42
|
+
ceil: (a) => Math.ceil(a),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formula functions: the arguments are a list.
|
|
47
|
+
* @type {Record<string, (args: number[]) => number>}
|
|
48
|
+
*/
|
|
49
|
+
const FUNCTIONS = {
|
|
50
|
+
min: (args) => Math.min.apply(Math, args),
|
|
51
|
+
max: (args) => Math.max.apply(Math, args),
|
|
52
|
+
round: (args) => Math.round(args[0]),
|
|
53
|
+
ceil: (args) => Math.ceil(args[0]),
|
|
54
|
+
floor: (args) => Math.floor(args[0]),
|
|
55
|
+
sqrt: (args) => Math.sqrt(args[0]),
|
|
56
|
+
pow: (args) => Math.pow(args[0], args[1]),
|
|
57
|
+
exp: (args) => Math.exp(args[0]),
|
|
58
|
+
log: (args) => Math.log(args[0]),
|
|
59
|
+
cos: (args) => Math.cos(args[0]),
|
|
60
|
+
sin: (args) => Math.sin(args[0]),
|
|
61
|
+
tan: (args) => Math.tan(args[0]),
|
|
62
|
+
acos: (args) => Math.acos(args[0]),
|
|
63
|
+
asin: (args) => Math.asin(args[0]),
|
|
64
|
+
atan: (args) => Math.atan(args[0]),
|
|
65
|
+
atan2: (args) => Math.atan2(args[0], args[1]),
|
|
66
|
+
random: () => Math.random(),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Binding strength of a formula's operation tokens: the rest bind tightest.
|
|
71
|
+
* @type {Record<string, number>}
|
|
72
|
+
*/
|
|
73
|
+
const PRECEDENCE = { add: 1, sub: 1, mul: 2, div: 2, mod: 2 }
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Unknown descriptor types already warned about
|
|
77
|
+
* @type {Record<string, boolean>}
|
|
78
|
+
*/
|
|
79
|
+
const warned = {}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether a value is a number the converters can work on.
|
|
83
|
+
* @param {any} value
|
|
84
|
+
*/
|
|
85
|
+
function numeric(value) {
|
|
86
|
+
const n = typeof value === 'number' ? value : parseFloat(value)
|
|
87
|
+
return isFinite(n) ? n : 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Applies a descriptor to a view-model value.
|
|
92
|
+
*
|
|
93
|
+
* @param {any} descriptor
|
|
94
|
+
* @param {any} value
|
|
95
|
+
* @param {ConvertContext} [context]
|
|
96
|
+
* @returns {any}
|
|
97
|
+
*/
|
|
98
|
+
export function applyConverter(descriptor, value, context) {
|
|
99
|
+
const ctx = context || {}
|
|
100
|
+
switch (descriptor && descriptor.type) {
|
|
101
|
+
case 'negate':
|
|
102
|
+
return !value
|
|
103
|
+
case 'range':
|
|
104
|
+
return range(descriptor, numeric(value))
|
|
105
|
+
case 'round':
|
|
106
|
+
return roundTo(numeric(value), descriptor.decimals)
|
|
107
|
+
case 'operation':
|
|
108
|
+
return operate(descriptor.op, numeric(value), operand(descriptor, ctx))
|
|
109
|
+
case 'formula':
|
|
110
|
+
return formula(descriptor.tokens || [], numeric(value), ctx)
|
|
111
|
+
case 'toString':
|
|
112
|
+
return toText(descriptor, value, ctx)
|
|
113
|
+
case 'toNumber':
|
|
114
|
+
return numeric(value)
|
|
115
|
+
case 'pad':
|
|
116
|
+
return pad(descriptor, String(value === undefined || value === null ? '' : value))
|
|
117
|
+
case 'trim': {
|
|
118
|
+
let text = String(value === undefined || value === null ? '' : value)
|
|
119
|
+
if (descriptor.trimStart) text = text.replace(/^\s+/, '')
|
|
120
|
+
if (descriptor.trimEnd) text = text.replace(/\s+$/, '')
|
|
121
|
+
return text
|
|
122
|
+
}
|
|
123
|
+
case 'removeZeros':
|
|
124
|
+
return trimZeros(String(value === undefined || value === null ? '' : value))
|
|
125
|
+
case 'enumToUint':
|
|
126
|
+
return enumIndex(value, ctx.names)
|
|
127
|
+
case 'listLength':
|
|
128
|
+
return Array.isArray(value) ? value.length : 0
|
|
129
|
+
case 'group': {
|
|
130
|
+
let result = value
|
|
131
|
+
const items = descriptor.items || []
|
|
132
|
+
// only the first link sees the enum names
|
|
133
|
+
for (let n = 0; n < items.length; n++) result = applyConverter(items[n], result, n === 0 ? ctx : { read: ctx.read, warn: ctx.warn })
|
|
134
|
+
return result
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
warnUnknown(descriptor, ctx)
|
|
138
|
+
return value
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Undoes a descriptor for a bind that writes a property back into the view
|
|
144
|
+
* model. What cannot be undone (a formula, a rounding) passes the value on.
|
|
145
|
+
*
|
|
146
|
+
* @param {any} descriptor
|
|
147
|
+
* @param {any} value the target property's value
|
|
148
|
+
* @param {ConvertContext} [context]
|
|
149
|
+
* @returns {any}
|
|
150
|
+
*/
|
|
151
|
+
export function reverseConverter(descriptor, value, context) {
|
|
152
|
+
const ctx = context || {}
|
|
153
|
+
switch (descriptor && descriptor.type) {
|
|
154
|
+
case 'negate':
|
|
155
|
+
return !value
|
|
156
|
+
case 'range':
|
|
157
|
+
return unrange(descriptor, numeric(value))
|
|
158
|
+
case 'operation': {
|
|
159
|
+
/** @type {Record<string, string>} */
|
|
160
|
+
const inverses = { add: 'sub', sub: 'add', mul: 'div', div: 'mul' }
|
|
161
|
+
const inverse = inverses[descriptor.op]
|
|
162
|
+
return inverse ? operate(inverse, numeric(value), operand(descriptor, ctx)) : value
|
|
163
|
+
}
|
|
164
|
+
case 'toString':
|
|
165
|
+
return numeric(value)
|
|
166
|
+
case 'toNumber':
|
|
167
|
+
return String(value)
|
|
168
|
+
case 'group': {
|
|
169
|
+
let result = value
|
|
170
|
+
const items = descriptor.items || []
|
|
171
|
+
for (let n = items.length - 1; n >= 0; n--) result = reverseConverter(items[n], result, ctx)
|
|
172
|
+
return result
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
return value
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Applies descriptors in order.
|
|
181
|
+
*
|
|
182
|
+
* @param {any} descriptor a descriptor, or null for none
|
|
183
|
+
* @param {any} value
|
|
184
|
+
* @param {ConvertContext} [context]
|
|
185
|
+
*/
|
|
186
|
+
export function convertValue(descriptor, value, context) {
|
|
187
|
+
return descriptor ? applyConverter(descriptor, value, context) : value
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {any} descriptor
|
|
192
|
+
* @param {ConvertContext} ctx
|
|
193
|
+
*/
|
|
194
|
+
function warnUnknown(descriptor, ctx) {
|
|
195
|
+
const type = descriptor && descriptor.type
|
|
196
|
+
if (warned[type] || !ctx.warn) return
|
|
197
|
+
warned[type] = true
|
|
198
|
+
ctx.warn("data converter '" + type + "' is not known to this player; its value passes through")
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The right-hand side of an operation: a literal, or another property's value.
|
|
203
|
+
*
|
|
204
|
+
* @param {any} descriptor
|
|
205
|
+
* @param {ConvertContext} ctx
|
|
206
|
+
*/
|
|
207
|
+
function operand(descriptor, ctx) {
|
|
208
|
+
if (typeof descriptor.path === 'string') return numeric(ctx.read ? ctx.read(descriptor.path) : 0)
|
|
209
|
+
return numeric(descriptor.value)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {string} op
|
|
214
|
+
* @param {number} a
|
|
215
|
+
* @param {number} b
|
|
216
|
+
*/
|
|
217
|
+
function operate(op, a, b) {
|
|
218
|
+
const fn = OPERATIONS[op]
|
|
219
|
+
return fn ? fn(a, b) : a
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {number} value
|
|
224
|
+
* @param {number} decimals
|
|
225
|
+
*/
|
|
226
|
+
function roundTo(value, decimals) {
|
|
227
|
+
const scale = Math.pow(10, decimals || 0)
|
|
228
|
+
return Math.round(value * scale) / scale
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A range mapper: the input's position in its range, placed in the output's.
|
|
233
|
+
*
|
|
234
|
+
* @param {any} d
|
|
235
|
+
* @param {number} input
|
|
236
|
+
*/
|
|
237
|
+
function range(d, input) {
|
|
238
|
+
const low = Math.min(d.minInput, d.maxInput)
|
|
239
|
+
const high = Math.max(d.minInput, d.maxInput)
|
|
240
|
+
let x = input
|
|
241
|
+
if (d.modulo && high > low) x = low + ((((x - low) % (high - low)) + (high - low)) % (high - low))
|
|
242
|
+
if (d.clampLower && x < low) x = low
|
|
243
|
+
if (d.clampUpper && x > high) x = high
|
|
244
|
+
|
|
245
|
+
const span = d.maxInput - d.minInput
|
|
246
|
+
let t = span === 0 ? 0 : (x - d.minInput) / span
|
|
247
|
+
if (d.hold) t = t >= 1 ? 1 : 0
|
|
248
|
+
if (d.reverse) t = 1 - t
|
|
249
|
+
return d.minOutput + t * (d.maxOutput - d.minOutput)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {any} d
|
|
254
|
+
* @param {number} output
|
|
255
|
+
*/
|
|
256
|
+
function unrange(d, output) {
|
|
257
|
+
const span = d.maxOutput - d.minOutput
|
|
258
|
+
let t = span === 0 ? 0 : (output - d.minOutput) / span
|
|
259
|
+
if (d.reverse) t = 1 - t
|
|
260
|
+
let x = d.minInput + t * (d.maxInput - d.minInput)
|
|
261
|
+
const low = Math.min(d.minInput, d.maxInput)
|
|
262
|
+
const high = Math.max(d.minInput, d.maxInput)
|
|
263
|
+
if (d.clampLower && x < low) x = low
|
|
264
|
+
if (d.clampUpper && x > high) x = high
|
|
265
|
+
return x
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* A formula's tokens read as an infix expression.
|
|
270
|
+
*
|
|
271
|
+
* @param {any[]} tokens
|
|
272
|
+
* @param {number} input
|
|
273
|
+
* @param {ConvertContext} ctx
|
|
274
|
+
*/
|
|
275
|
+
function formula(tokens, input, ctx) {
|
|
276
|
+
let at = 0
|
|
277
|
+
|
|
278
|
+
/** @param {number} minimum */
|
|
279
|
+
function expression(minimum) {
|
|
280
|
+
/** @type {number} */
|
|
281
|
+
let left = operandOf()
|
|
282
|
+
while (at < tokens.length && tokens[at].token === 'op') {
|
|
283
|
+
const op = tokens[at].op
|
|
284
|
+
const strength = PRECEDENCE[op] || 3
|
|
285
|
+
if (strength < minimum) break
|
|
286
|
+
at++
|
|
287
|
+
left = operate(op, left, expression(strength + 1))
|
|
288
|
+
}
|
|
289
|
+
return left
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** @returns {number} */
|
|
293
|
+
function operandOf() {
|
|
294
|
+
const token = tokens[at++]
|
|
295
|
+
if (!token) return 0
|
|
296
|
+
if (token.token === 'input') return input
|
|
297
|
+
if (token.token === 'value') return typeof token.path === 'string' ? numeric(ctx.read ? ctx.read(token.path) : 0) : numeric(token.value)
|
|
298
|
+
if (token.token === 'open') {
|
|
299
|
+
/** @type {number} */
|
|
300
|
+
const inner = expression(0)
|
|
301
|
+
if (at < tokens.length && tokens[at].token === 'close') at++
|
|
302
|
+
return inner
|
|
303
|
+
}
|
|
304
|
+
if (token.token === 'op' && token.op === 'sub') return -operandOf()
|
|
305
|
+
if (token.token === 'fn') {
|
|
306
|
+
const args = []
|
|
307
|
+
while (at < tokens.length && tokens[at].token !== 'close') {
|
|
308
|
+
args.push(expression(0))
|
|
309
|
+
if (at < tokens.length && tokens[at].token === 'sep') at++
|
|
310
|
+
}
|
|
311
|
+
at++
|
|
312
|
+
const fn = FUNCTIONS[token.fn]
|
|
313
|
+
return fn ? fn(args) : 0
|
|
314
|
+
}
|
|
315
|
+
return 0
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const result = expression(0)
|
|
319
|
+
return isFinite(result) ? result : 0
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* ToString: a number to text at the converter's precision, a colour to hex,
|
|
324
|
+
* an enum index to its name.
|
|
325
|
+
*
|
|
326
|
+
* @param {any} d
|
|
327
|
+
* @param {any} value
|
|
328
|
+
* @param {ConvertContext} ctx
|
|
329
|
+
*/
|
|
330
|
+
function toText(d, value, ctx) {
|
|
331
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false'
|
|
332
|
+
if (typeof value === 'string') return value
|
|
333
|
+
if (ctx.names && typeof value === 'number' && ctx.names[value] !== undefined) return ctx.names[value]
|
|
334
|
+
if (typeof value !== 'number') return value === undefined || value === null ? '' : String(value)
|
|
335
|
+
|
|
336
|
+
// Rive holds numbers as 32-bit floats, which rounds 25.55 to 25.5499992
|
|
337
|
+
const single = Math.fround ? Math.fround(value) : value
|
|
338
|
+
let text = d.round ? single.toFixed(d.decimals || 0) : single.toFixed(6)
|
|
339
|
+
if (d.trimZeros) text = trimZeros(text)
|
|
340
|
+
return text
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** @param {string} text */
|
|
344
|
+
function trimZeros(text) {
|
|
345
|
+
return text.indexOf('.') < 0 ? text : text.replace(/0+$/, '').replace(/\.$/, '')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @param {any} d
|
|
350
|
+
* @param {string} text
|
|
351
|
+
*/
|
|
352
|
+
function pad(d, text) {
|
|
353
|
+
const filler = String(d.text || '')
|
|
354
|
+
if (!filler) return text
|
|
355
|
+
const wanted = Math.max(d.length, text.length)
|
|
356
|
+
let result = text
|
|
357
|
+
while (result.length < wanted) result = d.atEnd ? result + filler : filler + result
|
|
358
|
+
return d.atEnd ? result.slice(0, wanted) : result.slice(result.length - wanted)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* An enum value as its index, from an index or a name.
|
|
363
|
+
*
|
|
364
|
+
* @param {any} value
|
|
365
|
+
* @param {string[]} [names]
|
|
366
|
+
*/
|
|
367
|
+
function enumIndex(value, names) {
|
|
368
|
+
if (typeof value === 'string' && names) {
|
|
369
|
+
const at = names.indexOf(value)
|
|
370
|
+
if (at >= 0) return at
|
|
371
|
+
}
|
|
372
|
+
return numeric(value)
|
|
373
|
+
}
|