@rafaelaugusto/jolt 0.1.0-beta.10 → 0.1.0-beta.12
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/README.md +55 -6
- package/dist/animate.d.ts +14 -4
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +50 -34
- package/dist/animate.js.map +1 -1
- package/dist/drag.d.ts +17 -3
- package/dist/drag.d.ts.map +1 -1
- package/dist/drag.js +34 -11
- package/dist/drag.js.map +1 -1
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/easing.d.ts +1 -1
- package/dist/easing.d.ts.map +1 -1
- package/dist/easing.js +11 -0
- package/dist/easing.js.map +1 -1
- package/dist/engine.d.ts +18 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +143 -36
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +253 -0
- package/dist/gradient.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +63 -10
- package/dist/keyboard.js.map +1 -1
- package/dist/mixer.d.ts +49 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixer.js +238 -0
- package/dist/mixer.js.map +1 -0
- package/dist/motion-scope.d.ts +4 -1
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-scope.js +8 -3
- package/dist/motion-scope.js.map +1 -1
- package/dist/motion-states.d.ts +18 -6
- package/dist/motion-states.d.ts.map +1 -1
- package/dist/motion-states.js +114 -26
- package/dist/motion-states.js.map +1 -1
- package/dist/motion-value.d.ts +115 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +437 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -2
- package/dist/path.js.map +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +31 -12
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +30 -4
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +63 -7
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +28 -2
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +78 -2
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrame.java +53 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrameFollower.java +57 -0
- package/platforms/android/proguard-rules.pro +2 -0
- package/src/android-plugin.d.ts +56 -0
- package/src/animate.ts +99 -16
- package/src/drag.ts +67 -21
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/easing.ts +14 -0
- package/src/engine.ts +158 -53
- package/src/gradient.ts +378 -0
- package/src/index.ts +34 -3
- package/src/keyboard.ts +83 -18
- package/src/mixer.ts +264 -0
- package/src/motion-scope.ts +14 -6
- package/src/motion-states.ts +145 -41
- package/src/motion-value.ts +563 -0
- package/src/path.ts +2 -1
- package/src/track.ts +37 -12
- package/src/transition.ts +99 -10
- package/src/types.ts +5 -0
- package/src/vue/index.ts +98 -1
package/src/drag.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type TouchGestureEventData,
|
|
6
6
|
type View,
|
|
7
7
|
} from '@nativescript/core'
|
|
8
|
-
import {
|
|
8
|
+
import { toView } from './animate.js'
|
|
9
9
|
import { DEFAULT_FRICTION } from './decay.js'
|
|
10
10
|
import { DragInput } from './drag-input.js'
|
|
11
11
|
import {
|
|
@@ -15,9 +15,18 @@ import {
|
|
|
15
15
|
solveDragRelease,
|
|
16
16
|
type DragCurve,
|
|
17
17
|
} from './drag-physics.js'
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
baseValue,
|
|
20
|
+
currentValue,
|
|
21
|
+
setBase,
|
|
22
|
+
stopBase,
|
|
23
|
+
ViewAnimation,
|
|
24
|
+
type Animation,
|
|
25
|
+
type Plan,
|
|
26
|
+
} from './engine.js'
|
|
27
|
+
import { asRoot, isMotionValue, type MotionValue } from './motion-value.js'
|
|
19
28
|
import type { PlaybackAnimation } from './playback.js'
|
|
20
|
-
import {
|
|
29
|
+
import { createTrack } from './track.js'
|
|
21
30
|
import type { Target } from './types.js'
|
|
22
31
|
|
|
23
32
|
export interface DragPoint {
|
|
@@ -29,10 +38,10 @@ export interface DragEvent extends DragPoint {
|
|
|
29
38
|
readonly velocityX: number
|
|
30
39
|
readonly velocityY: number
|
|
31
40
|
}
|
|
32
|
-
export interface DragRelease extends DragEvent {
|
|
41
|
+
export interface DragRelease<A extends Animation = PlaybackAnimation> extends DragEvent {
|
|
33
42
|
readonly target: DragPoint
|
|
34
43
|
/** False on interruption/cancellation. Check before applying completion effects. */
|
|
35
|
-
readonly animation:
|
|
44
|
+
readonly animation: A
|
|
36
45
|
}
|
|
37
46
|
export interface DragOptions {
|
|
38
47
|
/** Defaults to both. One finger only. */
|
|
@@ -51,6 +60,17 @@ export interface DragOptions {
|
|
|
51
60
|
onCancel?: (event: DragEvent) => void
|
|
52
61
|
onSettle?: (position: DragPoint) => void
|
|
53
62
|
}
|
|
63
|
+
/** Drags a motion value instead of the view. Bounds and snap points are in the value's units. */
|
|
64
|
+
export interface DragValueOptions extends Omit<
|
|
65
|
+
DragOptions,
|
|
66
|
+
'axis' | 'snap' | 'onRelease'
|
|
67
|
+
> {
|
|
68
|
+
/** Takes the position on every move and plays the release; bind views to it to follow. */
|
|
69
|
+
value: MotionValue
|
|
70
|
+
axis: 'x' | 'y'
|
|
71
|
+
snap?: readonly number[]
|
|
72
|
+
onRelease?: (event: DragRelease<Animation>) => void
|
|
73
|
+
}
|
|
54
74
|
export interface Drag {
|
|
55
75
|
readonly dragging: boolean
|
|
56
76
|
readonly settling: boolean
|
|
@@ -67,22 +87,28 @@ export interface Drag {
|
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
type Axis = 'x' | 'y'
|
|
90
|
+
type Root = ReturnType<typeof asRoot>
|
|
70
91
|
interface Settings {
|
|
71
92
|
axes: Axis[]
|
|
72
93
|
curves: Record<Axis, DragCurve>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
value: Root | undefined
|
|
95
|
+
callbacks: Pick<DragOptions, 'onStart' | 'onMove' | 'onCancel' | 'onSettle'> & {
|
|
96
|
+
onRelease?: (event: DragRelease<Animation>) => void
|
|
97
|
+
}
|
|
77
98
|
}
|
|
78
99
|
const owners = new WeakMap<View, Drag>()
|
|
79
100
|
|
|
80
|
-
function settings(options: DragOptions): Settings {
|
|
101
|
+
function settings(options: DragOptions | DragValueOptions): Settings {
|
|
81
102
|
if (!options || typeof options !== 'object')
|
|
82
103
|
throw new TypeError('Drag options must be an object.')
|
|
83
104
|
const axis = options.axis ?? 'both'
|
|
84
105
|
if (!['x', 'y', 'both'].includes(axis))
|
|
85
106
|
throw new TypeError('Drag axis must be x, y or both.')
|
|
107
|
+
const value = 'value' in options ? options.value : undefined
|
|
108
|
+
if (value !== undefined && !isMotionValue(value))
|
|
109
|
+
throw new TypeError('Drag value must be a motion value.')
|
|
110
|
+
if (value !== undefined && axis === 'both')
|
|
111
|
+
throw new TypeError('Dragging a motion value needs axis "x" or "y".')
|
|
86
112
|
const friction = options.friction ?? DEFAULT_FRICTION
|
|
87
113
|
if (!Number.isFinite(friction) || friction <= 0 || friction >= 1)
|
|
88
114
|
throw new RangeError('Drag friction must be between 0 and 1 exclusively.')
|
|
@@ -152,18 +178,23 @@ function settings(options: DragOptions): Settings {
|
|
|
152
178
|
const callbacks = {
|
|
153
179
|
onStart: options.onStart,
|
|
154
180
|
onMove: options.onMove,
|
|
155
|
-
onRelease: options.onRelease,
|
|
181
|
+
onRelease: options.onRelease as Settings['callbacks']['onRelease'],
|
|
156
182
|
onCancel: options.onCancel,
|
|
157
183
|
onSettle: options.onSettle,
|
|
158
184
|
}
|
|
159
185
|
for (const callback of Object.values(callbacks))
|
|
160
186
|
if (callback !== undefined && typeof callback !== 'function')
|
|
161
187
|
throw new TypeError('Drag callbacks must be functions.')
|
|
162
|
-
return { axes, curves, callbacks }
|
|
188
|
+
return { axes, curves, value: value && asRoot(value), callbacks }
|
|
163
189
|
}
|
|
164
190
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Main-thread pan updates; release physics play through Jolt's native animation drivers.
|
|
193
|
+
* With `value`, the gesture drives a motion value and the views bound to it.
|
|
194
|
+
*/
|
|
195
|
+
export function drag(target: Target, options: DragValueOptions): Drag
|
|
196
|
+
export function drag(target: Target, options?: DragOptions): Drag
|
|
197
|
+
export function drag(target: Target, options: DragOptions | DragValueOptions = {}): Drag {
|
|
167
198
|
const view = toView(target)
|
|
168
199
|
const config = settings(options) // Invalid replacements leave the existing owner intact.
|
|
169
200
|
const previous = owners.get(view)
|
|
@@ -187,7 +218,7 @@ class DragController implements Drag {
|
|
|
187
218
|
private attached = false
|
|
188
219
|
private blocked = false
|
|
189
220
|
private generation = 0
|
|
190
|
-
private release:
|
|
221
|
+
private release: Animation | undefined
|
|
191
222
|
private input = new DragInput()
|
|
192
223
|
private origin = { x: 0, y: 0 }
|
|
193
224
|
private last: DragEvent = { x: 0, y: 0, velocityX: 0, velocityY: 0 }
|
|
@@ -215,6 +246,8 @@ class DragController implements Drag {
|
|
|
215
246
|
return this.destroyed
|
|
216
247
|
}
|
|
217
248
|
get position(): DragPoint {
|
|
249
|
+
const value = this.config.value
|
|
250
|
+
if (value) return { x: 0, y: 0, [this.config.axes[0]!]: value.raw() }
|
|
218
251
|
return { x: currentValue(this.view, 'x'), y: currentValue(this.view, 'y') }
|
|
219
252
|
}
|
|
220
253
|
cancel = () => {
|
|
@@ -303,9 +336,15 @@ class DragController implements Drag {
|
|
|
303
336
|
this.cancel()
|
|
304
337
|
if (!this.attached || this.destroyed || this.disabled) return
|
|
305
338
|
this.blocked = false
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
339
|
+
const value = this.config.value
|
|
340
|
+
if (value) {
|
|
341
|
+
value.stop()
|
|
342
|
+
this.origin = this.position
|
|
343
|
+
} else {
|
|
344
|
+
// Claim just the enabled axes, preserving the native presentation on takeover.
|
|
345
|
+
stopBase(this.view, this.config.axes)
|
|
346
|
+
this.origin = { x: baseValue(this.view, 'x'), y: baseValue(this.view, 'y') }
|
|
347
|
+
}
|
|
309
348
|
const size =
|
|
310
349
|
(this.view.parent as View | undefined)?.getActualSize() ??
|
|
311
350
|
this.view.getActualSize()
|
|
@@ -346,8 +385,11 @@ class DragController implements Drag {
|
|
|
346
385
|
: clampDrag(raw, curve.bounds)
|
|
347
386
|
if (axis === 'x') vx = input.velocityX * resistance
|
|
348
387
|
else vy = input.velocityY * resistance
|
|
349
|
-
|
|
350
|
-
|
|
388
|
+
if (this.config.value) this.config.value.set(position[axis])
|
|
389
|
+
else {
|
|
390
|
+
stopBase(this.view, [axis])
|
|
391
|
+
setBase(this.view, axis, position[axis])
|
|
392
|
+
}
|
|
351
393
|
}
|
|
352
394
|
this.last = { ...position, velocityX: vx, velocityY: vy }
|
|
353
395
|
if (event.state !== 3)
|
|
@@ -383,7 +425,11 @@ class DragController implements Drag {
|
|
|
383
425
|
transition: { curve, delay: 0, repeat: 0, reverse: false },
|
|
384
426
|
}
|
|
385
427
|
})
|
|
386
|
-
const
|
|
428
|
+
const [axis] = this.config.axes
|
|
429
|
+
const value = this.config.value
|
|
430
|
+
const animation = value
|
|
431
|
+
? value.play(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
|
|
432
|
+
: new ViewAnimation(this.view, plans)
|
|
387
433
|
this.release = animation
|
|
388
434
|
void animation.finished.then((finished) => {
|
|
389
435
|
if (generation !== this.generation || this.destroyed) return
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Utils, type View } from '@nativescript/core'
|
|
2
|
+
import type { GradientFrame, GradientPainter, GradientType } from '../gradient.js'
|
|
3
|
+
|
|
4
|
+
const floats = (values: readonly number[]) => {
|
|
5
|
+
const array = Array.create('float', values.length)
|
|
6
|
+
values.forEach((value, i) => (array[i] = value))
|
|
7
|
+
return array
|
|
8
|
+
}
|
|
9
|
+
const ints = (values: readonly number[]) => {
|
|
10
|
+
const array = Array.create('int', values.length)
|
|
11
|
+
values.forEach((value, i) => (array[i] = value | 0))
|
|
12
|
+
return array
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A gradient drawable stacked on the view's own background: above its color, below its children. */
|
|
16
|
+
export function androidGradient(
|
|
17
|
+
view: View,
|
|
18
|
+
type: GradientType,
|
|
19
|
+
): GradientPainter | undefined {
|
|
20
|
+
let layer: org.rafaelaugusto.jolt.GradientLayer
|
|
21
|
+
try {
|
|
22
|
+
layer = new org.rafaelaugusto.jolt.GradientLayer(
|
|
23
|
+
type === 'linear' ? 0 : type === 'radial' ? 1 : 2,
|
|
24
|
+
)
|
|
25
|
+
} catch {
|
|
26
|
+
// A JS-only update cannot install the native plugin.
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
let stack: android.graphics.drawable.LayerDrawable | undefined
|
|
30
|
+
let queued = false
|
|
31
|
+
|
|
32
|
+
const wrap = () => {
|
|
33
|
+
const native = view.nativeViewProtected as android.view.View | undefined
|
|
34
|
+
if (!native) return
|
|
35
|
+
const corner = typeof view.borderRadius === 'number' ? view.borderRadius : 0
|
|
36
|
+
layer.setCorner(corner * Utils.layout.getDisplayDensity())
|
|
37
|
+
const background = native.getBackground()
|
|
38
|
+
if (stack && background === stack) return
|
|
39
|
+
const layers = Array.create(android.graphics.drawable.Drawable, 2)
|
|
40
|
+
layers[0] = background ?? new android.graphics.drawable.ColorDrawable(0)
|
|
41
|
+
layers[1] = layer
|
|
42
|
+
stack = new android.graphics.drawable.LayerDrawable(layers)
|
|
43
|
+
native.setBackground(stack)
|
|
44
|
+
}
|
|
45
|
+
// Core replaces the background drawable when its style changes; stack on the new one.
|
|
46
|
+
const rewrap = () => {
|
|
47
|
+
if (queued) return
|
|
48
|
+
queued = true
|
|
49
|
+
void Promise.resolve().then(() => {
|
|
50
|
+
queued = false
|
|
51
|
+
wrap()
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
view.on('loaded', wrap)
|
|
55
|
+
view.style.on('backgroundInternalChange', rewrap)
|
|
56
|
+
view.style.on('borderTopLeftRadiusChange', rewrap)
|
|
57
|
+
wrap()
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
paint(frame: GradientFrame) {
|
|
61
|
+
layer.paint(
|
|
62
|
+
ints(frame.colors),
|
|
63
|
+
floats(frame.stops),
|
|
64
|
+
frame.angle,
|
|
65
|
+
frame.x,
|
|
66
|
+
frame.y,
|
|
67
|
+
frame.radius,
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
play(frames, times, timing, onEnd) {
|
|
71
|
+
let active = true
|
|
72
|
+
layer.play(
|
|
73
|
+
floats(times),
|
|
74
|
+
ints(frames.flatMap((frame) => frame.colors)),
|
|
75
|
+
floats(frames.flatMap((frame) => frame.stops)),
|
|
76
|
+
floats(frames.flatMap((frame) => [frame.angle, frame.x, frame.y, frame.radius])),
|
|
77
|
+
Math.round(timing.duration),
|
|
78
|
+
Math.round(timing.delay),
|
|
79
|
+
timing.repeat === Infinity ? -1 : timing.repeat,
|
|
80
|
+
timing.reverse,
|
|
81
|
+
new org.rafaelaugusto.jolt.GradientLayer.Listener({
|
|
82
|
+
onEnd: (finished: boolean) => {
|
|
83
|
+
if (!active) return
|
|
84
|
+
active = false
|
|
85
|
+
onEnd(finished)
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
return {
|
|
90
|
+
cancel() {
|
|
91
|
+
if (!active) return
|
|
92
|
+
active = false
|
|
93
|
+
layer.cancel()
|
|
94
|
+
onEnd(false)
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
remove() {
|
|
99
|
+
view.off('loaded', wrap)
|
|
100
|
+
view.style.off('backgroundInternalChange', rewrap)
|
|
101
|
+
view.style.off('borderTopLeftRadiusChange', rewrap)
|
|
102
|
+
layer.cancel()
|
|
103
|
+
const native = view.nativeViewProtected as android.view.View | undefined
|
|
104
|
+
if (native && stack && native.getBackground() === stack)
|
|
105
|
+
native.setBackground(stack.getDrawable(0))
|
|
106
|
+
stack = undefined
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Color, type View } from '@nativescript/core'
|
|
2
|
+
import {
|
|
3
|
+
gradientPoints,
|
|
4
|
+
type GradientFrame,
|
|
5
|
+
type GradientPainter,
|
|
6
|
+
type GradientType,
|
|
7
|
+
} from '../gradient.js'
|
|
8
|
+
|
|
9
|
+
const keys = ['colors', 'locations', 'startPoint', 'endPoint'] as const
|
|
10
|
+
|
|
11
|
+
/** A CAGradientLayer at the back of the view's layer: above its background, below its children. */
|
|
12
|
+
export function iosGradient(view: View, type: GradientType): GradientPainter {
|
|
13
|
+
const layer = CAGradientLayer.new()
|
|
14
|
+
layer.type =
|
|
15
|
+
type === 'linear'
|
|
16
|
+
? kCAGradientLayerAxial
|
|
17
|
+
: type === 'radial'
|
|
18
|
+
? kCAGradientLayerRadial
|
|
19
|
+
: kCAGradientLayerConic
|
|
20
|
+
let current: GradientFrame | undefined
|
|
21
|
+
let generation = 0
|
|
22
|
+
|
|
23
|
+
const native = () => view.nativeViewProtected as UIView | undefined
|
|
24
|
+
const size = () => {
|
|
25
|
+
const bounds = native()?.layer.bounds
|
|
26
|
+
return { width: bounds?.size.width ?? 0, height: bounds?.size.height ?? 0 }
|
|
27
|
+
}
|
|
28
|
+
const values = (frame: GradientFrame) => {
|
|
29
|
+
const { width, height } = size()
|
|
30
|
+
const { start, end: toward } = gradientPoints(type, frame, width, height)
|
|
31
|
+
// Core Animation starts a conic gradient on the side opposite its end point.
|
|
32
|
+
const end =
|
|
33
|
+
type === 'conic' ? { x: 2 * start.x - toward.x, y: 2 * start.y - toward.y } : toward
|
|
34
|
+
return {
|
|
35
|
+
colors: NSArray.arrayWithArray(
|
|
36
|
+
frame.colors.map((argb) => new Color(argb).ios.CGColor),
|
|
37
|
+
),
|
|
38
|
+
locations: NSArray.arrayWithArray(frame.stops),
|
|
39
|
+
startPoint: NSValue.valueWithCGPoint(CGPointMake(start.x, start.y)),
|
|
40
|
+
endPoint: NSValue.valueWithCGPoint(CGPointMake(end.x, end.y)),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const attach = () => {
|
|
44
|
+
const host = native()
|
|
45
|
+
if (!host) return
|
|
46
|
+
CATransaction.begin()
|
|
47
|
+
CATransaction.setDisableActions(true)
|
|
48
|
+
if (layer.superlayer !== host.layer) host.layer.insertSublayerAtIndex(layer, 0)
|
|
49
|
+
layer.frame = host.layer.bounds
|
|
50
|
+
layer.cornerRadius = host.layer.cornerRadius
|
|
51
|
+
if (current) {
|
|
52
|
+
const model = values(current)
|
|
53
|
+
layer.colors = model.colors
|
|
54
|
+
layer.locations = model.locations
|
|
55
|
+
layer.startPoint = model.startPoint.CGPointValue
|
|
56
|
+
layer.endPoint = model.endPoint.CGPointValue
|
|
57
|
+
}
|
|
58
|
+
CATransaction.commit()
|
|
59
|
+
}
|
|
60
|
+
view.on('loaded', attach)
|
|
61
|
+
view.on('layoutChanged', attach)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
paint(frame) {
|
|
65
|
+
current = frame
|
|
66
|
+
generation++
|
|
67
|
+
for (const key of keys) layer.removeAnimationForKey(`jolt:gradient:${key}`)
|
|
68
|
+
attach()
|
|
69
|
+
},
|
|
70
|
+
play(frames, times, timing, onEnd) {
|
|
71
|
+
const instance = ++generation
|
|
72
|
+
current = frames[frames.length - 1]!
|
|
73
|
+
attach()
|
|
74
|
+
const sampled = frames.map(values)
|
|
75
|
+
const now = CACurrentMediaTime()
|
|
76
|
+
CATransaction.begin()
|
|
77
|
+
if (timing.repeat !== Infinity)
|
|
78
|
+
CATransaction.setCompletionBlock(() => {
|
|
79
|
+
if (instance === generation) onEnd(true)
|
|
80
|
+
})
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
const animation = CAKeyframeAnimation.animationWithKeyPath(key)
|
|
83
|
+
animation.values = NSArray.arrayWithArray(sampled.map((value) => value[key]))
|
|
84
|
+
animation.keyTimes = NSArray.arrayWithArray(times as number[])
|
|
85
|
+
animation.duration = Math.max(timing.duration, 1) / 1000
|
|
86
|
+
animation.beginTime = layer.convertTimeFromLayer(now + timing.delay / 1000, null)
|
|
87
|
+
animation.fillMode = kCAFillModeBackwards
|
|
88
|
+
if (timing.repeat > 0) {
|
|
89
|
+
animation.autoreverses = timing.reverse
|
|
90
|
+
animation.repeatCount = timing.reverse
|
|
91
|
+
? (timing.repeat + 1) / 2
|
|
92
|
+
: timing.repeat + 1
|
|
93
|
+
}
|
|
94
|
+
layer.addAnimationForKey(animation, `jolt:gradient:${key}`)
|
|
95
|
+
}
|
|
96
|
+
CATransaction.commit()
|
|
97
|
+
return {
|
|
98
|
+
cancel() {
|
|
99
|
+
if (instance !== generation) return
|
|
100
|
+
generation++
|
|
101
|
+
for (const key of keys) layer.removeAnimationForKey(`jolt:gradient:${key}`)
|
|
102
|
+
onEnd(false)
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
remove() {
|
|
107
|
+
generation++
|
|
108
|
+
view.off('loaded', attach)
|
|
109
|
+
view.off('layoutChanged', attach)
|
|
110
|
+
layer.removeAllAnimations()
|
|
111
|
+
layer.removeFromSuperlayer()
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/easing.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type EasingName =
|
|
|
15
15
|
| 'backOut'
|
|
16
16
|
| 'backInOut'
|
|
17
17
|
| 'anticipate'
|
|
18
|
+
| `${'quad' | 'cubic' | 'quart' | 'quint' | 'sine' | 'expo'}${'In' | 'Out' | 'InOut'}`
|
|
18
19
|
|
|
19
20
|
export type Easing = EasingName | CubicBezier | EasingFunction
|
|
20
21
|
|
|
@@ -73,6 +74,13 @@ const mirror =
|
|
|
73
74
|
(p) =>
|
|
74
75
|
p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2
|
|
75
76
|
|
|
77
|
+
const family = <N extends string>(name: N, easeIn: EasingFunction) =>
|
|
78
|
+
({
|
|
79
|
+
[`${name}In`]: easeIn,
|
|
80
|
+
[`${name}Out`]: reverse(easeIn),
|
|
81
|
+
[`${name}InOut`]: mirror(easeIn),
|
|
82
|
+
}) as Record<`${N}${'In' | 'Out' | 'InOut'}`, EasingFunction>
|
|
83
|
+
|
|
76
84
|
const easings: Record<EasingName, EasingFunction> = {
|
|
77
85
|
linear,
|
|
78
86
|
ease: cubicBezier(0.25, 0.1, 0.25, 1),
|
|
@@ -87,6 +95,12 @@ const easings: Record<EasingName, EasingFunction> = {
|
|
|
87
95
|
backInOut: mirror(backIn),
|
|
88
96
|
anticipate: (p) =>
|
|
89
97
|
p <= 0.5 ? backIn(2 * p) / 2 : p >= 1 ? 1 : 1 - Math.pow(2, 10 - 20 * p) / 2,
|
|
98
|
+
...family('quad', (p) => p ** 2),
|
|
99
|
+
...family('cubic', (p) => p ** 3),
|
|
100
|
+
...family('quart', (p) => p ** 4),
|
|
101
|
+
...family('quint', (p) => p ** 5),
|
|
102
|
+
...family('sine', (p) => (p >= 1 ? 1 : 1 - Math.cos((p * Math.PI) / 2))),
|
|
103
|
+
...family('expo', (p) => (p <= 0 ? 0 : 2 ** (10 * p - 10))),
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
export function resolveEasing(easing: Easing): EasingFunction {
|