rotor-framework 0.3.2

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 (39) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +120 -0
  3. package/package.json +59 -0
  4. package/src/source/RotorFramework.bs +654 -0
  5. package/src/source/RotorFrameworkTask.bs +278 -0
  6. package/src/source/base/BaseModel.bs +52 -0
  7. package/src/source/base/BasePlugin.bs +48 -0
  8. package/src/source/base/BaseReducer.bs +184 -0
  9. package/src/source/base/BaseStack.bs +92 -0
  10. package/src/source/base/BaseViewModel.bs +124 -0
  11. package/src/source/base/BaseWidget.bs +104 -0
  12. package/src/source/base/DispatcherCreator.bs +193 -0
  13. package/src/source/base/DispatcherExternal.bs +260 -0
  14. package/src/source/base/ListenerForDispatchers.bs +246 -0
  15. package/src/source/engine/Constants.bs +74 -0
  16. package/src/source/engine/animator/Animator.bs +334 -0
  17. package/src/source/engine/builder/Builder.bs +213 -0
  18. package/src/source/engine/builder/NodePool.bs +236 -0
  19. package/src/source/engine/builder/PluginAdapter.bs +139 -0
  20. package/src/source/engine/builder/PostProcessor.bs +331 -0
  21. package/src/source/engine/builder/Processor.bs +156 -0
  22. package/src/source/engine/builder/Tree.bs +278 -0
  23. package/src/source/engine/builder/TreeBase.bs +313 -0
  24. package/src/source/engine/builder/WidgetCreate.bs +322 -0
  25. package/src/source/engine/builder/WidgetRemove.bs +72 -0
  26. package/src/source/engine/builder/WidgetUpdate.bs +113 -0
  27. package/src/source/engine/providers/Dispatcher.bs +72 -0
  28. package/src/source/engine/providers/DispatcherProvider.bs +95 -0
  29. package/src/source/engine/services/I18n.bs +169 -0
  30. package/src/source/libs/animate/Animate.bs +753 -0
  31. package/src/source/libs/animate/LICENSE.txt +21 -0
  32. package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
  33. package/src/source/plugins/FieldsPlugin.bs +180 -0
  34. package/src/source/plugins/FocusPlugin.bs +1522 -0
  35. package/src/source/plugins/FontStylePlugin.bs +159 -0
  36. package/src/source/plugins/ObserverPlugin.bs +548 -0
  37. package/src/source/utils/ArrayUtils.bs +495 -0
  38. package/src/source/utils/GeneralUtils.bs +181 -0
  39. package/src/source/utils/NodeUtils.bs +180 -0
@@ -0,0 +1,753 @@
1
+ ' Public API
2
+ namespace animate
3
+ function create(params as object) as object
4
+ animation = _animate.create(params)
5
+ return _animate.utils.createNodeFromAA(animation)
6
+ end function
7
+
8
+ ' Returns a random integer between min and max, inclusive
9
+ function random(min as integer, max as integer) as integer
10
+ return rnd(max - min) + min
11
+ end function
12
+
13
+ function timeline(params as object) as object
14
+ return new _animate.Timeline(params)
15
+ end function
16
+
17
+ ' All of these have been generated with GPT4,
18
+ ' hopefully the math is ok :)
19
+ namespace penner
20
+ ' The *Sine functions create animations that accelerate
21
+ ' or decelerate according to a sinusoidal curve.
22
+ function easeInSine(t as float) as float
23
+ return -1 * cos(t * _animate.utils.constants.radiansFactor) + 1
24
+ end function
25
+
26
+ function easeOutSine(t as float) as float
27
+ return sin(t * _animate.utils.constants.radiansFactor)
28
+ end function
29
+
30
+ function easeInOutSine(t as float) as float
31
+ return -0.5 * (cos(_animate.utils.constants.pi * t) - 1)
32
+ end function
33
+
34
+ function easeOutInSine(t as float) as float
35
+ if (t < 0.5)
36
+ return animate.penner.easeOutSine(t * 2) / 2
37
+ else
38
+ return animate.penner.easeInSine((t - 0.5) * 2) / 2 + 0.5
39
+ end if
40
+ end function
41
+
42
+ ' The *Circ functions create an animation that
43
+ ' accelerates and decelerates respectively in a manner that resembles
44
+ ' a quarter of a circle.
45
+
46
+ function easeInCirc(t as float) as float
47
+ return -1 * (sqr(1 - t * t) - 1)
48
+ end function
49
+
50
+ function easeOutCirc(t as float) as float
51
+ t = t - 1
52
+ return sqr(1 - t * t)
53
+ end function
54
+
55
+ function easeInOutCirc(t as float) as float
56
+ if (t < 0.5)
57
+ return -0.5 * (sqr(1 - 4 * t * t) - 1)
58
+ else
59
+ t = t - 1
60
+ return 0.5 * (sqr(1 - 4 * t * t) + 1)
61
+ end if
62
+ end function
63
+
64
+ function easeOutInCirc(t as float) as float
65
+ if (t < 0.5)
66
+ return animate.penner.easeOutCirc(t * 2) / 2
67
+ else
68
+ return animate.penner.easeInCirc((t - 0.5) * 2) / 2 + 0.5
69
+ end if
70
+ end function
71
+
72
+ ' The *Elastic functions create animations that overshoot
73
+ ' the final state and then oscillate around it before settling,
74
+ ' creating an elastic effect.
75
+ ' `p` is the period of the oscillation
76
+ ' `s` is the phase shift of the oscillation
77
+ ' These values have been chosen empirically.
78
+
79
+ function easeInElastic(t as Float) as Float
80
+ if (t = 0)
81
+ return 0
82
+ end if
83
+ if (t = 1)
84
+ return 1
85
+ end if
86
+ p = 0.3
87
+ s = p / 4
88
+ t = t - 1
89
+ return -(2 ^ (10 * t) * sin((t - s) * (2 * _animate.utils.constants.pi) / p))
90
+ end function
91
+
92
+ function easeOutElastic(t as Float) as Float
93
+ if (t = 0)
94
+ return 0
95
+ end if
96
+ if (t = 1)
97
+ return 1
98
+ end if
99
+ p = 0.3
100
+ s = p / 4
101
+ return 2 ^ (-10 * t) * sin((t - s) * (2 * _animate.utils.constants.pi) / p) + 1
102
+ end function
103
+
104
+ function easeInOutElastic(t as Float) as Float
105
+ if (t = 0)
106
+ return 0
107
+ end if
108
+ if (t = 1)
109
+ return 1
110
+ end if
111
+ p = 0.45
112
+ s = p / 4
113
+ if (t < 0.5)
114
+ t = t - 1
115
+ return -0.5 * (2 ^ (10 * t) * sin((t - s) * (2 * _animate.utils.constants.pi) / p))
116
+ else
117
+ t = t - 1
118
+ return 0.5 * (2 ^ (-10 * t) * sin((t - s) * (2 * _animate.utils.constants.pi) / p)) + 1
119
+ end if
120
+ end function
121
+
122
+ function easeOutInElastic(t as Float) as Float
123
+ if (t < 0.5)
124
+ return animate.penner.easeOutElastic(t * 2) / 2
125
+ else
126
+ return animate.penner.easeInElastic((t - 0.5) * 2) / 2 + 0.5
127
+ end if
128
+ end function
129
+
130
+ ' The *Back functions create an animation
131
+ ' that overshoots the final state and then comes back.
132
+ ' This creates a "pullback" or "draw back" effect at the beginning
133
+ ' or end of the animation, respectively.
134
+ ' `s` is a constant that determines the amount of overshoot.
135
+ ' The value 1.70158 results in about a 10% overshoot.
136
+
137
+ function easeInBack(t as float) as float
138
+ s = 1.70158
139
+ return t * t * ((s + 1) * t - s)
140
+ end function
141
+
142
+ function easeOutBack(t as float) as float
143
+ s = 1.70158
144
+ t = t - 1
145
+ return (t * t * ((s + 1) * t + s) + 1)
146
+ end function
147
+
148
+ function easeInOutBack(t as float) as float
149
+ ' Adjust the overshoot to achieve a visually
150
+ ' pleasing result for the combined "in" and "out" animation.
151
+ s = 1.70158 * 1.525
152
+ if (t < 0.5)
153
+ return (t * t * ((s + 1) * t - s)) / 2
154
+ else
155
+ t = t - 2
156
+ return (t * t * ((s + 1) * t + s) + 2) / 2
157
+ end if
158
+ end function
159
+
160
+ function easeOutInBack(t as float) as float
161
+ if (t < 0.5)
162
+ return animate.penner.easeOutBack(t * 2) / 2
163
+ else
164
+ return animate.penner.easeInBack((t - 0.5) * 2) / 2 + 0.5
165
+ end if
166
+ end function
167
+
168
+ ' The *Bounce functions create animations that mimic a bouncing effect.
169
+ ' The constants in the function (7.5625, 2.75, etc.)
170
+ ' are chosen empirically to make the bounce effect look good.
171
+
172
+ function easeInBounce(t as float) as float
173
+ return 1 - animate.penner.easeOutBounce(1 - t)
174
+ end function
175
+
176
+ function easeOutBounce(t as float) as float
177
+ if (t < (1 / 2.75))
178
+ return (7.5625 * t * t)
179
+ else if (t < (2 / 2.75))
180
+ t = t - (1.5 / 2.75)
181
+ return (7.5625 * t * t + 0.75)
182
+ else if (t < (2.5 / 2.75))
183
+ t = t - (2.25 / 2.75)
184
+ return (7.5625 * t * t + 0.9375)
185
+ else
186
+ t = t - (2.625 / 2.75)
187
+ return (7.5625 * t * t + 0.984375)
188
+ end if
189
+ end function
190
+
191
+ function easeInOutBounce(t as float) as float
192
+ if (t < 0.5)
193
+ return animate.penner.easeInBounce(t * 2) / 2
194
+ else
195
+ return animate.penner.easeOutBounce(t * 2 - 1) / 2 + 0.5
196
+ end if
197
+ end function
198
+
199
+ function easeOutInBounce(t as float) as float
200
+ if (t < 0.5)
201
+ return animate.penner.easeOutBounce(t * 2) / 2
202
+ else
203
+ return animate.penner.easeInBounce((t - 0.5) * 2) / 2 + 0.5
204
+ end if
205
+ end function
206
+ end namespace
207
+ end namespace
208
+
209
+ ' Private methods
210
+ namespace _animate
211
+ function create(params as object, recursive = true as boolean) as object
212
+ animations = []
213
+ animationParams = _animate.utils.extractAnimationParams(params)
214
+ if (animationParams.duration = invalid)
215
+ animationParams.duration = 1
216
+ end if
217
+
218
+ for each animationKey in animationParams.keys()
219
+ params.delete(animationKey)
220
+ end for
221
+
222
+ interpolatorParams = _animate.utils.extractInterpolatorParams(params)
223
+ for each interpolatorKey in interpolatorParams.keys()
224
+ params.delete(interpolatorKey)
225
+ end for
226
+
227
+ targets = []
228
+ if (params.target <> invalid)
229
+ targets = params.target
230
+ params.delete("target")
231
+ end if
232
+
233
+ if (params.targets <> invalid)
234
+ targets = params.targets
235
+ params.delete("targets")
236
+ end if
237
+
238
+ autoplay = params.autoplay
239
+ if (autoplay = invalid)
240
+ autoplay = false
241
+ end if
242
+ params.delete("autoplay")
243
+
244
+ nodeTargets = getNodeTargets(targets)
245
+
246
+ subAnimations = []
247
+
248
+ if (recursive)
249
+ subKeys = []
250
+
251
+ for each item in params.items()
252
+ if (_animate.is._aa(item.value))
253
+ subParams = {}
254
+ subParams.append(animationParams)
255
+ subParams.append(interpolatorParams)
256
+ subParams.targets = nodeTargets
257
+ subParams[item.key] = item.value.value
258
+ item.value.delete("value")
259
+ subParams.append(item.value)
260
+ subAnimation = _animate.create(subParams, false)
261
+ subAnimations.push(_animate.utils.shallowCopy(subAnimation))
262
+ subKeys.push(item.key)
263
+ end if
264
+ end for
265
+
266
+ for each subKey in subKeys
267
+ params.delete(subKey)
268
+ end for
269
+ end if
270
+
271
+ customEaseFunction = invalid
272
+ if (_animate.is.func(animationParams.easeFunction))
273
+ customEaseFunction = animationParams.easeFunction
274
+ animationParams.easeFunction = "linear"
275
+ end if
276
+
277
+ targetsLength = nodeTargets.count()
278
+ for i = 0 to targetsLength - 1
279
+ nodeTarget = nodeTargets[i]
280
+ if (nodeTarget.id = invalid or nodeTarget.id = "")
281
+ nodeTarget.id = `__animate_${rnd(42069)}` ' nice
282
+ end if
283
+
284
+ targetInfo = {
285
+ nodeTarget: nodeTarget,
286
+ index: i,
287
+ targetsLength: targetsLength
288
+ }
289
+
290
+ finalAnimationParams = _animate.utils.getFunctionValueParams(animationParams, targetInfo)
291
+ finalInterpolatorParams = _animate.utils.getFunctionValueParams(interpolatorParams, targetInfo)
292
+ finalParams = _animate.utils.getFunctionValueParams(params, targetInfo)
293
+
294
+ alternate = (finalParams.direction = "alternate")
295
+ if (alternate)
296
+ finalParams.direction = "normal"
297
+ end if
298
+
299
+ animation = {
300
+ subtype: _animate.utils.nodes.Animation,
301
+ children: _animate.getTargetInterpolators(nodeTarget, finalParams, _animate.utils.shallowCopy(finalInterpolatorParams), finalAnimationParams.duration, customEaseFunction)
302
+ }
303
+
304
+ if (animation.children.count() > 0)
305
+ if (alternate)
306
+ alternatedParams = _animate.utils.shallowCopy(finalParams)
307
+ alternatedParams.direction = "reverse"
308
+ alternated = {
309
+ subtype: _animate.utils.nodes.Animation,
310
+ children: _animate.getTargetInterpolators(nodeTarget, alternatedParams, _animate.utils.shallowCopy(finalInterpolatorParams), finalAnimationParams.duration, customEaseFunction)
311
+ }
312
+
313
+ animation.append(finalAnimationParams)
314
+ animation.repeat = false
315
+
316
+ alternated.append(finalAnimationParams)
317
+ alternated.repeat = false
318
+
319
+ seqAnim = {
320
+ subtype: _animate.utils.nodes.SequentialAnimation,
321
+ children: [_animate.utils.shallowCopy(animation), alternated]
322
+ }
323
+ seqAnim.repeat = finalAnimationParams.repeat
324
+ animations.push(seqAnim)
325
+ else
326
+ animation.append(finalAnimationParams)
327
+ animations.push(_animate.utils.shallowCopy(animation))
328
+ end if
329
+ end if
330
+ end for
331
+
332
+ if (recursive and subAnimations.count() > 0)
333
+ animations.append(subAnimations)
334
+ end if
335
+
336
+ if (animations.count() > 1)
337
+ multipleAnimations = {
338
+ subtype: _animate.utils.nodes.ParallelAnimation,
339
+ children: animations
340
+ }
341
+ if (recursive)
342
+ multipleAnimations.control = autoplay ? "start" : "none"
343
+ end if
344
+ return multipleAnimations
345
+ else if (animations.count() = 1)
346
+ if (recursive)
347
+ animations[0].control = autoplay ? "start" : "none"
348
+ end if
349
+ return animations[0]
350
+ end if
351
+
352
+ return animations
353
+ end function
354
+
355
+ function getTargetInterpolators(nodeTarget as object, params as object, globalInterpolatorParams as object, duration as float, customEaseFunction as dynamic) as object
356
+ interpolators = []
357
+ direction = params.direction
358
+ if (direction = invalid)
359
+ direction = "normal"
360
+ end if
361
+ params.delete("direction")
362
+
363
+ for each item in params.items()
364
+ ' Thanks Roku for not letting us split if statements
365
+ ' in multiple lines. This is very readable! :)
366
+ if (_animate.is.valid(item.value) and (_animate.is.special.prop(item.key) or nodeTarget.hasField(item.key)))
367
+ interpolator = _animate.getPropertyInterpolator(nodeTarget, item.key, item.value, duration, customEaseFunction)
368
+ if (interpolator <> invalid)
369
+ finalInterpolator = globalInterpolatorParams
370
+ finalInterpolator.append(interpolator)
371
+
372
+ if (direction = "reverse")
373
+ finalInterpolator.keyValue.reverse()
374
+ end if
375
+
376
+ interpolators.push(_animate.utils.shallowCopy(finalInterpolator))
377
+ end if
378
+ end if
379
+ end for
380
+ return interpolators
381
+ end function
382
+
383
+ function getPropertyInterpolator(nodeTarget as object, property as string, value as dynamic, duration, customEaseFunction as dynamic) as dynamic
384
+ if (property = "translation" and _animate.is.arr2d(value))
385
+ value = [
386
+ nodeTarget.translation[0] + cdbl(value[0]),
387
+ nodeTarget.translation[1] + cdbl(value[1])
388
+ ]
389
+ else if (_animate.is.special.translation(property) and _animate.utils.compareFieldStrings(property, "translationTo") and _animate.is.arr2d(value))
390
+ value = [
391
+ cdbl(value[0]),
392
+ cdbl(value[1])
393
+ ]
394
+ property = "translation"
395
+ else if (_animate.is.special.translation(property))
396
+ value = cdbl(value)
397
+ if (_animate.utils.compareFieldStrings(property, "x"))
398
+ value = [value, nodeTarget.translation[1]]
399
+ else if (_animate.utils.compareFieldStrings(property, "y"))
400
+ value = [nodeTarget.translation[0], value]
401
+ else if (_animate.utils.compareFieldStrings(property, "translateX"))
402
+ currentX = nodeTarget.translation[0]
403
+ value = [currentX + value, nodeTarget.translation[1]]
404
+ else if (_animate.utils.compareFieldStrings(property, "translateY"))
405
+ currentY = nodeTarget.translation[1]
406
+ value = [nodeTarget.translation[0], currentY + value]
407
+ end if
408
+ property = "translation"
409
+ end if
410
+
411
+ ops = GetInterface(value, "ifStringOps")
412
+
413
+ if (property = "rotation" and _animate.is.str(value) and ops <> invalid and ops.endsWith("deg"))
414
+ degrees = value.tokenize("deg")[0].toFloat()
415
+ value = degrees * _animate.utils.constants.radiansFactor
416
+ end if
417
+
418
+ if (property = "scale" and _animate.is.number(value))
419
+ if (_animate.is.integer(value))
420
+ value = cdbl(value)
421
+ end if
422
+ value = [value, value]
423
+ end if
424
+
425
+ currentValue = nodeTarget[property]
426
+
427
+ interpolator = {}
428
+ if (_animate.is.colorField(property))
429
+ interpolator.subtype = _animate.utils.nodes.ColorFieldInterpolator
430
+ else if (_animate.is.arr2d(value))
431
+ interpolator.subtype = _animate.utils.nodes.Vector2DFieldInterpolator
432
+ else if (_animate.is.number(value))
433
+ interpolator.subtype = _animate.utils.nodes.FloatFieldInterpolator
434
+ else
435
+ return invalid
436
+ end if
437
+
438
+ keyValue = [currentValue, value]
439
+
440
+ if (not _animate.is.colorField(property) and customEaseFunction <> invalid)
441
+ keyValue = _animate.utils.generateKeyValues(currentValue, value, 60 * duration, customEaseFunction)
442
+ end if
443
+
444
+ keyValueCount = keyValue.count()
445
+ key = []
446
+ _step = 1 / (keyValueCount - 1)
447
+ for i = 0 to keyValueCount - 1
448
+ key.push(i * _step)
449
+ end for
450
+
451
+ interpolator.append({
452
+ fieldToInterp: `${nodeTarget.id}.${property}`,
453
+ keyValue: keyValue,
454
+ key: key
455
+ })
456
+
457
+ return interpolator
458
+ end function
459
+
460
+ function getNodeTargets(targets as dynamic) as object
461
+ nodeTargets = []
462
+
463
+ if (_animate.is.str(targets))
464
+ ' For a string `targets`, we split by spaces
465
+ ' and try to find the nodes in the current `m.top` scope.
466
+ ids = targets.tokenize(" ")
467
+ for each id in ids
468
+ nodeTarget = m.top.findNode(id)
469
+ if (_animate.is.valid(nodeTarget))
470
+ nodeTargets.push(nodeTarget)
471
+ end if
472
+ end for
473
+ else if (_animate.is.arr(targets))
474
+ ' For array targets, we recursively find targets inside
475
+ ' each element.
476
+ for each target in targets
477
+ nodeTargets.append(getNodeTargets(target))
478
+ end for
479
+ else if (_animate.is._node(targets))
480
+ ' For nodes we consider them directly
481
+ nodeTargets.push(targets)
482
+ end if
483
+
484
+ return nodeTargets
485
+ end function
486
+
487
+ class Timeline
488
+ private nodeObject as object = {}
489
+ private inheritableParams as object = {}
490
+
491
+ sub new(params as object)
492
+ sequential = false
493
+ if (params.sequential <> invalid and _animate.is.bool(params.sequential))
494
+ sequential = params.sequential
495
+ params.delete("sequential")
496
+ end if
497
+
498
+ animationAndInterpolatorParams = _animate.utils.extractAnimationParams(params)
499
+ animationAndInterpolatorParams.append(_animate.utils.extractInterpolatorParams(params))
500
+
501
+ m.inheritableParams = _animate.utils.extractParams(params, [
502
+ "targets",
503
+ "target",
504
+ "easeFunction",
505
+ "duration",
506
+ "delay"
507
+ ])
508
+
509
+ nodeName = _animate.utils.nodes.ParallelAnimation
510
+ if (sequential)
511
+ nodeName = _animate.utils.nodes.SequentialAnimation
512
+ end if
513
+
514
+ m.nodeObject = {
515
+ subtype: nodeName,
516
+ children: []
517
+ }
518
+ m.nodeObject.append(animationAndInterpolatorParams)
519
+ end sub
520
+
521
+ function add(params as object) as object
522
+ if (params.target <> invalid)
523
+ params.targets = params.target
524
+ params.delete("target")
525
+ end if
526
+
527
+ finalParams = _animate.utils.shallowCopy(m.inheritableParams)
528
+ finalParams.append(params)
529
+ m.nodeObject.children.push(_animate.create(finalParams))
530
+ return m
531
+ end function
532
+
533
+ function getAnimation() as object
534
+ return _animate.utils.createNodeFromAA(m.nodeObject)
535
+ end function
536
+ end class
537
+
538
+ namespace is
539
+ function valid(x as dynamic) as boolean
540
+ return x <> invalid
541
+ end function
542
+
543
+ function bool(x as dynamic) as boolean
544
+ return _animate.is.valid(x) and getInterface(x, "ifBoolean") <> invalid
545
+ end function
546
+
547
+ function str(x as dynamic) as boolean
548
+ return _animate.is.valid(x) and getInterface(x, "ifString") <> invalid
549
+ end function
550
+
551
+ function arr(x as dynamic) as boolean
552
+ return _animate.is.valid(x) and getInterface(x, "ifArray") <> invalid
553
+ end function
554
+
555
+ function integer(x as dynamic) as boolean
556
+ return _animate.is.valid(x) and getInterface(x, "ifInt") <> invalid and (type(x) = "roInt" or type(x) = "roInteger" or type(x) = "Integer")
557
+ end function
558
+
559
+ function float(x as dynamic) as boolean
560
+ return _animate.is.valid(x) and (getInterface(x, "ifFloat") <> invalid or (type(x) = "roFloat" or type(x) = "Float"))
561
+ end function
562
+
563
+ function number(x as dynamic) as boolean
564
+ return _animate.is.float(x) or _animate.is.integer(x)
565
+ end function
566
+
567
+ function _node(x as dynamic) as boolean
568
+ return _animate.is.valid(x) and getInterface(x, "ifSGNodeField") <> invalid
569
+ end function
570
+
571
+ function arr2d(x as dynamic) as boolean
572
+ return _animate.is.valid(x) and _animate.is.arr(x) and x.count() = 2 and _animate.is.number(x[0]) and _animate.is.number(x[1])
573
+ end function
574
+
575
+ function _aa(x as dynamic) as boolean
576
+ return _animate.is.valid(x) and getInterface(x, "ifAssociativeArray") <> invalid
577
+ end function
578
+
579
+ function func(x as dynamic) as boolean
580
+ return _animate.is.valid(x) and getInterface(x, "ifFunction") <> invalid
581
+ end function
582
+
583
+ function colorField(x as dynamic) as boolean
584
+ colorFields = ["color", "blendColor"]
585
+ if (not _animate.is.str(x))
586
+ return false
587
+ end if
588
+ for each cf in colorFields
589
+ if (_animate.utils.compareFieldStrings(cf, x))
590
+ return true
591
+ end if
592
+ end for
593
+ return false
594
+ end function
595
+
596
+ namespace special
597
+ function prop(x as dynamic) as boolean
598
+ return _animate.is.special.translation(x)
599
+ end function
600
+
601
+ function translation(x as dynamic) as boolean
602
+ if (not _animate.is.str(x))
603
+ return false
604
+ end if
605
+
606
+ specialProperties = ["x", "y", "translateX", "translateY", "translationTo"]
607
+ for each specialProperty in specialProperties
608
+ if (_animate.utils.compareFieldStrings(specialProperty, x))
609
+ return true
610
+ end if
611
+ end for
612
+
613
+ return false
614
+ end function
615
+ end namespace
616
+ end namespace
617
+
618
+ namespace utils
619
+
620
+ function createNodeFromAA(aa as object) as object
621
+ ' Make sure we have an associative array to process
622
+ if Type(aa) <> "roAssociativeArray"
623
+ return invalid
624
+ end if
625
+
626
+ ' Fetch subtype if it exists, otherwise provide a default
627
+ nodeSubtype = "Group" ' Example default node type
628
+ if aa.Lookup("subtype") <> invalid
629
+ nodeSubtype = aa.subtype
630
+ end if
631
+
632
+ ' Create a new node with the given (or default) subtype
633
+ node = CreateObject("roSGNode", nodeSubtype)
634
+
635
+ ' If we have a "children" key and it's an array, recurse
636
+ if aa.Lookup("children") <> invalid and Type(aa.children) = "roArray"
637
+ for each childAA in aa.children
638
+ ' Recursively create child nodes
639
+ childNode = _animate.utils.createNodeFromAA(childAA)
640
+ if childNode <> invalid
641
+ node.appendChild(childNode)
642
+ end if
643
+ end for
644
+ end if
645
+ ' Clean up the keys we no longer need in this AA
646
+ aa.delete("subtype")
647
+ aa.delete("children")
648
+
649
+ ' Set all remaining fields on the node from the AA
650
+ node.update(aa, true)
651
+
652
+ return node
653
+ end function
654
+
655
+ function compareFieldStrings(x as string, y as string) as boolean
656
+ return lcase(x) = lcase(y)
657
+ end function
658
+
659
+ function getAnimationParams() as object
660
+ return [
661
+ "repeat",
662
+ "delay",
663
+ "duration",
664
+ "easeFunction",
665
+ "easeInPercent",
666
+ "easeOutPercent",
667
+ "optional"
668
+ ]
669
+ end function
670
+
671
+ function getInterpolatorParams() as object
672
+ return [
673
+ "fraction",
674
+ "reverse"
675
+ ]
676
+ end function
677
+
678
+ function extractParams(source as object, wantedParams as object) as object
679
+ result = {}
680
+ for each wantedParam in wantedParams
681
+ if (source[wantedParam] <> invalid)
682
+ result[wantedParam] = source[wantedParam]
683
+ end if
684
+ end for
685
+ return result
686
+ end function
687
+
688
+ function extractAnimationParams(params as object) as object
689
+ return _animate.utils.extractParams(params, _animate.utils.getAnimationParams())
690
+ end function
691
+
692
+ function extractInterpolatorParams(params as object) as object
693
+ return _animate.utils.extractParams(params, _animate.utils.getInterpolatorParams())
694
+ end function
695
+
696
+ function shallowCopy(x as object) as object
697
+ y = {}
698
+ if (_animate.is.arr(x))
699
+ y = []
700
+ end if
701
+ y.append(x)
702
+ return y
703
+ end function
704
+
705
+ function getFunctionValueParams(params as object, targetInfo as object) as object
706
+ copy = _animate.utils.shallowCopy(params)
707
+ for each item in copy.items()
708
+ if (_animate.is.func(item.value))
709
+ copy[item.key] = item.value(targetInfo.nodeTarget, targetInfo.index, targetInfo.targetsLength)
710
+ end if
711
+ end for
712
+ return copy
713
+ end function
714
+
715
+ function generateKeyValues(_start as dynamic, _end as dynamic, frames as float, easeFunc as function)
716
+ keyFrames = []
717
+ for frame = 0 to cint(frames) - 1
718
+ ' Normalize the current frame number to the range [0, 1]
719
+ normalizedFrame = frame / (frames - 1)
720
+ ' Apply the easing function
721
+ easedFrame = easeFunc(normalizedFrame)
722
+
723
+ if (_animate.is.arr2d(_start) and _animate.is.arr2d(_end))
724
+ ' Map the result to the range [start, end] for each component
725
+ d1 = _start[0] + (_end[0] - _start[0]) * easedFrame
726
+ d2 = _start[1] + (_end[1] - _start[1]) * easedFrame
727
+ value = [d1, d2]
728
+ else
729
+ ' Map the result to the range [start, end]
730
+ value = _start + (_end - _start) * easedFrame
731
+ end if
732
+
733
+ ' Add the calculated value to the list of keyframes
734
+ keyFrames.push(value)
735
+ end for
736
+ return keyFrames
737
+ end function
738
+
739
+ enum nodes
740
+ Animation = "Animation"
741
+ ParallelAnimation = "ParallelAnimation"
742
+ SequentialAnimation = "SequentialAnimation"
743
+ ColorFieldInterpolator = "ColorFieldInterpolator"
744
+ Vector2DFieldInterpolator = "Vector2DFieldInterpolator"
745
+ FloatFieldInterpolator = "FloatFieldInterpolator"
746
+ end enum
747
+
748
+ enum constants
749
+ pi = 3.14159265
750
+ radiansFactor = 0.01745329
751
+ end enum
752
+ end namespace
753
+ end namespace