@rafaelaugusto/jolt 0.1.0-beta.3 → 0.1.0-beta.5
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 +78 -13
- package/dist/animate.d.ts +3 -2
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +35 -5
- package/dist/animate.js.map +1 -1
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +73 -0
- package/dist/color.js.map +1 -0
- package/dist/decoration.d.ts +18 -0
- package/dist/decoration.d.ts.map +1 -0
- package/dist/decoration.js +155 -0
- package/dist/decoration.js.map +1 -0
- package/dist/driver/android-color.d.ts +20 -0
- package/dist/driver/android-color.d.ts.map +1 -0
- package/dist/driver/android-color.js +136 -0
- package/dist/driver/android-color.js.map +1 -0
- package/dist/driver/android-decoration.d.ts +22 -0
- package/dist/driver/android-decoration.d.ts.map +1 -0
- package/dist/driver/android-decoration.js +139 -0
- package/dist/driver/android-decoration.js.map +1 -0
- package/dist/driver/android-playback.d.ts +13 -0
- package/dist/driver/android-playback.d.ts.map +1 -0
- package/dist/driver/android-playback.js +141 -0
- package/dist/driver/android-playback.js.map +1 -0
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +28 -35
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +8 -1
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.d.ts +16 -0
- package/dist/driver/ios-color.d.ts.map +1 -0
- package/dist/driver/ios-color.js +392 -0
- package/dist/driver/ios-color.js.map +1 -0
- package/dist/driver/ios-decoration.d.ts +17 -0
- package/dist/driver/ios-decoration.d.ts.map +1 -0
- package/dist/driver/ios-decoration.js +271 -0
- package/dist/driver/ios-decoration.js.map +1 -0
- package/dist/driver/ios-playback.d.ts +10 -0
- package/dist/driver/ios-playback.d.ts.map +1 -0
- package/dist/driver/ios-playback.js +33 -0
- package/dist/driver/ios-playback.js.map +1 -0
- package/dist/driver/ios-shadow.d.ts +6 -0
- package/dist/driver/ios-shadow.d.ts.map +1 -0
- package/dist/driver/ios-shadow.js +52 -0
- package/dist/driver/ios-shadow.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +78 -35
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +48 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +217 -21
- package/dist/engine.js.map +1 -1
- package/dist/haptics.d.ts +4 -1
- package/dist/haptics.d.ts.map +1 -1
- package/dist/haptics.js +27 -3
- package/dist/haptics.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/liquid-grid.d.ts +3 -0
- package/dist/liquid-grid.d.ts.map +1 -0
- package/dist/liquid-grid.js +24 -0
- package/dist/liquid-grid.js.map +1 -0
- package/dist/liquid.d.ts +18 -0
- package/dist/liquid.d.ts.map +1 -0
- package/dist/liquid.js +229 -0
- package/dist/liquid.js.map +1 -0
- package/dist/playback-group.d.ts +6 -0
- package/dist/playback-group.d.ts.map +1 -0
- package/dist/playback-group.js +113 -0
- package/dist/playback-group.js.map +1 -0
- package/dist/playback.d.ts +25 -0
- package/dist/playback.d.ts.map +1 -0
- package/dist/playback.js +35 -0
- package/dist/playback.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +6 -1
- package/dist/press.js.map +1 -1
- package/dist/properties.d.ts +5 -2
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +25 -1
- package/dist/properties.js.map +1 -1
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +6 -0
- package/dist/sequence.js.map +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +3 -0
- package/dist/text.js.map +1 -1
- package/dist/track.d.ts +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +48 -3
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +11 -2
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
- package/platforms/android/proguard-rules.pro +7 -0
- package/src/animate.ts +48 -9
- package/src/color.ts +101 -0
- package/src/decoration.ts +180 -0
- package/src/driver/android-color.ts +170 -0
- package/src/driver/android-decoration.ts +182 -0
- package/src/driver/android-playback.ts +157 -0
- package/src/driver/android.ts +50 -46
- package/src/driver/index.ts +19 -1
- package/src/driver/ios-color.ts +441 -0
- package/src/driver/ios-decoration.ts +332 -0
- package/src/driver/ios-playback.ts +37 -0
- package/src/driver/ios-shadow.ts +50 -0
- package/src/driver/ios.ts +91 -42
- package/src/engine.ts +227 -23
- package/src/haptics.ts +25 -2
- package/src/index.ts +4 -0
- package/src/liquid-grid.ts +28 -0
- package/src/liquid.ts +282 -0
- package/src/playback-group.ts +110 -0
- package/src/playback.ts +62 -0
- package/src/press.ts +8 -2
- package/src/properties.ts +31 -5
- package/src/sequence.ts +5 -0
- package/src/text.ts +2 -0
- package/src/track.ts +72 -3
- package/src/transition.ts +13 -2
- package/src/types.ts +20 -0
package/src/engine.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClock,
|
|
3
|
+
validateProgress,
|
|
4
|
+
type PlaybackAnimation,
|
|
5
|
+
type PlaybackClock,
|
|
6
|
+
type PlaybackCommand,
|
|
7
|
+
} from './playback.js'
|
|
1
8
|
import type { View } from '@nativescript/core'
|
|
2
9
|
import { getDriver } from './driver/index.js'
|
|
3
10
|
import { playHaptics, type HapticOptions } from './haptics.js'
|
|
4
11
|
import { readValue, writeValue, type Property } from './properties.js'
|
|
5
12
|
import { createTrack, type Track } from './track.js'
|
|
6
13
|
import type { ResolvedTransition } from './transition.js'
|
|
14
|
+
import { isColorProperty } from './color.js'
|
|
7
15
|
|
|
8
16
|
export interface Animation extends PromiseLike<boolean> {
|
|
9
17
|
/**
|
|
@@ -30,8 +38,14 @@ interface Entry {
|
|
|
30
38
|
readonly owner: ViewAnimation
|
|
31
39
|
handle: unknown
|
|
32
40
|
done: boolean
|
|
41
|
+
finished: boolean
|
|
42
|
+
generation: number
|
|
43
|
+
token: symbol
|
|
44
|
+
completedValue?: number
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
const revisions = new WeakMap<View, Map<Property, symbol>>()
|
|
48
|
+
|
|
35
49
|
const running = new WeakMap<View, Map<Property, Entry>>()
|
|
36
50
|
const arrivals = new WeakMap<Animation, Promise<unknown>>()
|
|
37
51
|
|
|
@@ -47,7 +61,7 @@ export function setArrival(animation: Animation, arrival: Promise<unknown>) {
|
|
|
47
61
|
arrivals.set(animation, arrival)
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
export class ViewAnimation implements
|
|
64
|
+
export class ViewAnimation implements PlaybackAnimation {
|
|
51
65
|
readonly finished: Promise<boolean>
|
|
52
66
|
private resolve!: (finished: boolean) => void
|
|
53
67
|
private readonly entries: Entry[] = []
|
|
@@ -58,12 +72,180 @@ export class ViewAnimation implements Animation {
|
|
|
58
72
|
private arrive!: () => void
|
|
59
73
|
private cancelArrival: (() => void) | undefined
|
|
60
74
|
private haptics: ReturnType<typeof playHaptics>
|
|
75
|
+
private clock: PlaybackClock | undefined
|
|
76
|
+
private _paused = false
|
|
77
|
+
private controlled = false
|
|
78
|
+
private pendingProgress: number | undefined
|
|
79
|
+
private total = 0
|
|
80
|
+
private started = false
|
|
81
|
+
private arrival = 0
|
|
82
|
+
private arrived = false
|
|
83
|
+
private idleListener: (() => void) | undefined
|
|
84
|
+
private readyListener: (() => void) | undefined
|
|
85
|
+
private readonly endless: boolean
|
|
86
|
+
|
|
87
|
+
get paused() {
|
|
88
|
+
return this._paused
|
|
89
|
+
}
|
|
90
|
+
get duration() {
|
|
91
|
+
return this.started ? this.total : this.endless ? Infinity : 0
|
|
92
|
+
}
|
|
93
|
+
get currentTime() {
|
|
94
|
+
return this.time()
|
|
95
|
+
}
|
|
96
|
+
get ready() {
|
|
97
|
+
return this.started || this.settled
|
|
98
|
+
}
|
|
99
|
+
get idle() {
|
|
100
|
+
return this.settled || (this.started && this.pending === 0)
|
|
101
|
+
}
|
|
102
|
+
get progress() {
|
|
103
|
+
if (!Number.isFinite(this.duration))
|
|
104
|
+
throw new RangeError('Progress requires a finite animation.')
|
|
105
|
+
if (this.settled && this.complete) return 1
|
|
106
|
+
return (
|
|
107
|
+
this.pendingProgress ??
|
|
108
|
+
(this.duration ? Math.min(1, this.time() / this.duration) : this.settled ? 1 : 0)
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
set progress(value: number) {
|
|
112
|
+
validateProgress(value, this.duration)
|
|
113
|
+
if (this.settled) return
|
|
114
|
+
if (!this.started) {
|
|
115
|
+
this.pendingProgress = value
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
this.seekTime(this.duration * value)
|
|
119
|
+
}
|
|
120
|
+
onIdle(callback: () => void) {
|
|
121
|
+
this.idleListener = callback
|
|
122
|
+
}
|
|
123
|
+
onReady(callback: () => void) {
|
|
124
|
+
this.readyListener = callback
|
|
125
|
+
}
|
|
126
|
+
releaseGroup() {
|
|
127
|
+
this.held = false
|
|
128
|
+
if (this.idle && !this._paused) this.settle()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pause() {
|
|
132
|
+
if (this.settled || this._paused) return
|
|
133
|
+
this.controlled = true
|
|
134
|
+
this._paused = true
|
|
135
|
+
this.clock?.pause()
|
|
136
|
+
this.cancelArrival?.()
|
|
137
|
+
this.haptics?.pause()
|
|
138
|
+
if (this.started) this.controlTracks(this.time(), 'pause')
|
|
139
|
+
}
|
|
140
|
+
play() {
|
|
141
|
+
if (this.settled || !this._paused) return
|
|
142
|
+
this.controlled = true
|
|
143
|
+
this._paused = false
|
|
144
|
+
this.clock?.play()
|
|
145
|
+
if (this.started) {
|
|
146
|
+
this.controlTracks(this.time(), 'play')
|
|
147
|
+
this.scheduleArrival()
|
|
148
|
+
this.haptics?.play()
|
|
149
|
+
if (this.pending === 0) this.becameIdle()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
seekTime(time: number) {
|
|
153
|
+
if (this.settled) return
|
|
154
|
+
this.controlled = true
|
|
155
|
+
const position = Math.max(0, Math.min(this.total, time))
|
|
156
|
+
this.clock?.seek(position)
|
|
157
|
+
if (!this._paused) this.clock?.play()
|
|
158
|
+
for (const entry of this.entries) {
|
|
159
|
+
if (
|
|
160
|
+
entry.done &&
|
|
161
|
+
entry.finished &&
|
|
162
|
+
revisions.get(this.view)?.get(entry.track.property) === entry.token &&
|
|
163
|
+
readValue(this.view, entry.track.property) === entry.completedValue
|
|
164
|
+
) {
|
|
165
|
+
entry.done = false
|
|
166
|
+
entry.finished = false
|
|
167
|
+
this.pending++
|
|
168
|
+
entriesOf(this.view).set(entry.track.property, entry)
|
|
169
|
+
this.playEntry(entry, position)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.controlTracks(position)
|
|
173
|
+
this.scheduleArrival()
|
|
174
|
+
this.haptics?.seek()
|
|
175
|
+
if (this.pending === 0) this.becameIdle()
|
|
176
|
+
}
|
|
177
|
+
elapsed(entry: Entry, now: number) {
|
|
178
|
+
return this.controlled
|
|
179
|
+
? this.time()
|
|
180
|
+
: (getDriver().elapsed?.(entry.handle) ?? now - entry.startedAt)
|
|
181
|
+
}
|
|
182
|
+
private time() {
|
|
183
|
+
return this.clock?.time() ?? 0
|
|
184
|
+
}
|
|
185
|
+
private controlTracks(position: number, command: PlaybackCommand = 'seek') {
|
|
186
|
+
const driver = getDriver()
|
|
187
|
+
for (const entry of this.entries)
|
|
188
|
+
if (!entry.done)
|
|
189
|
+
driver.control?.(this.view, entry.handle, position, this._paused, command)
|
|
190
|
+
}
|
|
191
|
+
private scheduleArrival() {
|
|
192
|
+
this.cancelArrival?.()
|
|
193
|
+
this.cancelArrival = undefined
|
|
194
|
+
if (
|
|
195
|
+
this.pending === 0 ||
|
|
196
|
+
this.arrived ||
|
|
197
|
+
this._paused ||
|
|
198
|
+
!Number.isFinite(this.arrival) ||
|
|
199
|
+
this.total <= this.arrival
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
const remaining = this.arrival - this.time()
|
|
203
|
+
if (remaining <= 0) {
|
|
204
|
+
this.arrived = true
|
|
205
|
+
this.arrive()
|
|
206
|
+
} else
|
|
207
|
+
this.cancelArrival = getDriver().after(remaining, () => {
|
|
208
|
+
this.arrived = true
|
|
209
|
+
this.arrive()
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
private becameIdle() {
|
|
213
|
+
this.clock?.pause()
|
|
214
|
+
this.idleListener?.()
|
|
215
|
+
if (!this.complete || (!this.held && !this._paused)) this.settle()
|
|
216
|
+
}
|
|
217
|
+
private playEntry(entry: Entry, elapsed = 0) {
|
|
218
|
+
const initial = entry.generation === 0
|
|
219
|
+
const instance = ++entry.generation
|
|
220
|
+
const driver = getDriver()
|
|
221
|
+
entry.handle = driver.play(
|
|
222
|
+
this.view,
|
|
223
|
+
entry.track,
|
|
224
|
+
initial ? entry.startedAt : driver.now() - elapsed,
|
|
225
|
+
(finished = true) => {
|
|
226
|
+
if (entry.done || instance !== entry.generation) return
|
|
227
|
+
entry.done = true
|
|
228
|
+
entry.finished = finished
|
|
229
|
+
entry.completedValue = readValue(this.view, entry.track.property)
|
|
230
|
+
forget(entry)
|
|
231
|
+
this.ended(finished)
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
private unloaded = () => this.stop()
|
|
61
236
|
|
|
62
237
|
constructor(
|
|
63
238
|
readonly view: View,
|
|
64
239
|
plans: readonly Plan[],
|
|
65
240
|
private readonly haptic?: HapticOptions,
|
|
241
|
+
private held = false,
|
|
66
242
|
) {
|
|
243
|
+
this.endless = plans.some(
|
|
244
|
+
(plan) =>
|
|
245
|
+
!Number.isFinite(plan.transition.repeat) ||
|
|
246
|
+
(plan.transition.curve.type === 'tween' &&
|
|
247
|
+
!Number.isFinite(plan.transition.curve.duration)),
|
|
248
|
+
)
|
|
67
249
|
this.finished = new Promise((resolve) => (this.resolve = resolve))
|
|
68
250
|
setArrival(this, new Promise<void>((resolve) => (this.arrive = resolve)))
|
|
69
251
|
if (view.isLoaded && view.nativeViewProtected) this.start(plans)
|
|
@@ -94,10 +276,11 @@ export class ViewAnimation implements Animation {
|
|
|
94
276
|
this.complete = false
|
|
95
277
|
this.haptics?.cancel()
|
|
96
278
|
}
|
|
97
|
-
if (--this.pending === 0) this.
|
|
279
|
+
if (--this.pending === 0) this.becameIdle()
|
|
98
280
|
}
|
|
99
281
|
|
|
100
282
|
private start(plans: readonly Plan[]) {
|
|
283
|
+
this.started = true
|
|
101
284
|
const driver = getDriver()
|
|
102
285
|
const now = driver.now()
|
|
103
286
|
const entries = entriesOf(this.view)
|
|
@@ -109,16 +292,19 @@ export class ViewAnimation implements Animation {
|
|
|
109
292
|
if (previous) {
|
|
110
293
|
const elapsed = elapsedOf(previous, now)
|
|
111
294
|
current = driver.presented(this.view, property) ?? previous.track.valueAt(elapsed)
|
|
112
|
-
velocity = previous.track.velocityAt(elapsed)
|
|
295
|
+
velocity = previous.owner.paused ? 0 : previous.track.velocityAt(elapsed)
|
|
113
296
|
release(previous, now, false)
|
|
114
297
|
} else {
|
|
115
|
-
current =
|
|
298
|
+
current =
|
|
299
|
+
(isColorProperty(property)
|
|
300
|
+
? driver.presented(this.view, property)
|
|
301
|
+
: undefined) ?? readValue(this.view, property)
|
|
116
302
|
}
|
|
117
303
|
return createTrack(
|
|
118
304
|
property,
|
|
119
305
|
keyframes.map((value) => {
|
|
120
306
|
if (value === null) return current
|
|
121
|
-
if (!relative) return value
|
|
307
|
+
if (!relative || isColorProperty(property)) return value
|
|
122
308
|
return property === 'opacity' || property.startsWith('scale')
|
|
123
309
|
? current * value
|
|
124
310
|
: current + value
|
|
@@ -129,8 +315,13 @@ export class ViewAnimation implements Animation {
|
|
|
129
315
|
})
|
|
130
316
|
|
|
131
317
|
for (const track of tracks) writeValue(this.view, track.property, track.to)
|
|
318
|
+
driver.sync?.(this.view)
|
|
132
319
|
|
|
133
320
|
for (const track of tracks) {
|
|
321
|
+
let tokens = revisions.get(this.view)
|
|
322
|
+
if (!tokens) revisions.set(this.view, (tokens = new Map()))
|
|
323
|
+
const token = Symbol(track.property)
|
|
324
|
+
tokens.set(track.property, token)
|
|
134
325
|
if (track.end === 0) continue
|
|
135
326
|
const entry: Entry = {
|
|
136
327
|
track,
|
|
@@ -138,33 +329,40 @@ export class ViewAnimation implements Animation {
|
|
|
138
329
|
owner: this,
|
|
139
330
|
handle: undefined,
|
|
140
331
|
done: false,
|
|
332
|
+
finished: false,
|
|
333
|
+
generation: 0,
|
|
334
|
+
token,
|
|
141
335
|
}
|
|
142
336
|
entries.set(track.property, entry)
|
|
143
337
|
this.entries.push(entry)
|
|
144
338
|
this.pending++
|
|
145
339
|
}
|
|
146
340
|
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
if (this.
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
|
|
341
|
+
this.total = Math.max(0, ...tracks.map((track) => track.end))
|
|
342
|
+
this.arrival = Math.max(0, ...tracks.map((track) => track.arrival))
|
|
343
|
+
this.clock =
|
|
344
|
+
(this.pending ? driver.clock?.(this.total) : undefined) ??
|
|
345
|
+
createClock(() => driver.now())
|
|
346
|
+
if (this._paused) this.clock.pause()
|
|
347
|
+
this.view.on('unloaded', this.unloaded)
|
|
348
|
+
this.haptics = playHaptics(this.view, tracks, this.haptic, driver, now, () =>
|
|
349
|
+
this.time(),
|
|
350
|
+
)
|
|
351
|
+
if (this._paused) this.haptics?.pause()
|
|
352
|
+
for (const entry of this.entries) this.playEntry(entry)
|
|
353
|
+
if (this.pendingProgress !== undefined) {
|
|
354
|
+
const progress = this.pendingProgress
|
|
355
|
+
this.pendingProgress = undefined
|
|
356
|
+
this.seekTime(progress * this.total)
|
|
357
|
+
} else if (this._paused) this.controlTracks(0, 'pause')
|
|
358
|
+
this.scheduleArrival()
|
|
359
|
+
this.readyListener?.()
|
|
360
|
+
if (this.pending === 0) this.becameIdle()
|
|
163
361
|
}
|
|
164
362
|
|
|
165
363
|
private waitForLoad(plans: readonly Plan[]) {
|
|
166
364
|
for (const { property, keyframes, relative } of plans) {
|
|
167
|
-
if (relative) continue
|
|
365
|
+
if (relative && !isColorProperty(property)) continue
|
|
168
366
|
const first = keyframes[0]
|
|
169
367
|
if (first !== null && first !== undefined) writeValue(this.view, property, first)
|
|
170
368
|
}
|
|
@@ -183,6 +381,8 @@ export class ViewAnimation implements Animation {
|
|
|
183
381
|
if (this.settled) return
|
|
184
382
|
this.settled = true
|
|
185
383
|
this.cancelArrival?.()
|
|
384
|
+
this.clock?.cancel()
|
|
385
|
+
this.view.off('unloaded', this.unloaded)
|
|
186
386
|
if (this.complete) this.haptics?.finish()
|
|
187
387
|
else this.haptics?.cancel()
|
|
188
388
|
this.haptics = undefined
|
|
@@ -193,6 +393,9 @@ export class ViewAnimation implements Animation {
|
|
|
193
393
|
|
|
194
394
|
/** Stops whatever is animating these properties of the view, or all of them. */
|
|
195
395
|
export function stopView(view: View, properties?: readonly Property[]) {
|
|
396
|
+
const tokens = revisions.get(view)
|
|
397
|
+
for (const property of properties ?? tokens?.keys() ?? [])
|
|
398
|
+
tokens?.set(property, Symbol(property))
|
|
196
399
|
const entries = running.get(view)
|
|
197
400
|
if (!entries) return
|
|
198
401
|
const now = getDriver().now()
|
|
@@ -222,11 +425,12 @@ function release(entry: Entry, now: number, commit: boolean) {
|
|
|
222
425
|
const value =
|
|
223
426
|
driver.presented(view, property) ?? entry.track.valueAt(elapsedOf(entry, now))
|
|
224
427
|
writeValue(view, property, value)
|
|
428
|
+
driver.sync?.(view, property)
|
|
225
429
|
}
|
|
226
430
|
driver.stop(view, entry.handle)
|
|
227
431
|
entry.owner.ended(false)
|
|
228
432
|
}
|
|
229
433
|
|
|
230
434
|
function elapsedOf(entry: Entry, now: number) {
|
|
231
|
-
return
|
|
435
|
+
return entry.owner.elapsed(entry, now)
|
|
232
436
|
}
|
package/src/haptics.ts
CHANGED
|
@@ -48,6 +48,7 @@ export function playHaptics(
|
|
|
48
48
|
options: HapticOptions | undefined,
|
|
49
49
|
driver: Driver,
|
|
50
50
|
startedAt: number,
|
|
51
|
+
elapsedTime = () => driver.now() - startedAt,
|
|
51
52
|
) {
|
|
52
53
|
if (!isIOS || !options || typeof UIImpactFeedbackGenerator === 'undefined') return
|
|
53
54
|
const moving = tracks.filter(
|
|
@@ -69,6 +70,7 @@ export function playHaptics(
|
|
|
69
70
|
let generator: UIImpactFeedbackGenerator | undefined =
|
|
70
71
|
UIImpactFeedbackGenerator.alloc().initWithStyle(styles[options.style])
|
|
71
72
|
const native = view.nativeViewProtected as UIView
|
|
73
|
+
let paused = false
|
|
72
74
|
let next = 0
|
|
73
75
|
let cancelPrepare: (() => void) | undefined
|
|
74
76
|
let cancelImpact: (() => void) | undefined
|
|
@@ -79,6 +81,11 @@ export function playHaptics(
|
|
|
79
81
|
!!native.window &&
|
|
80
82
|
UIApplication.sharedApplication.applicationState === 0 // UIApplicationState.Active
|
|
81
83
|
|
|
84
|
+
const cancelTimers = () => {
|
|
85
|
+
cancelPrepare?.()
|
|
86
|
+
cancelImpact?.()
|
|
87
|
+
cancelPrepare = cancelImpact = undefined
|
|
88
|
+
}
|
|
82
89
|
const cancel = () => {
|
|
83
90
|
cancelPrepare?.()
|
|
84
91
|
cancelImpact?.()
|
|
@@ -88,7 +95,7 @@ export function playHaptics(
|
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
const fire = (tolerance = 0) => {
|
|
91
|
-
const elapsed =
|
|
98
|
+
const elapsed = elapsedTime()
|
|
92
99
|
// A busy UI thread can delay timers. Drop stale impacts instead of replaying
|
|
93
100
|
// several old taps at once when it becomes responsive again.
|
|
94
101
|
while (next < offsets.length && offsets[next]! <= elapsed + tolerance) {
|
|
@@ -98,8 +105,9 @@ export function playHaptics(
|
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
const schedule = () => {
|
|
108
|
+
if (paused) return
|
|
101
109
|
if (!generator || next === offsets.length) return cancel()
|
|
102
|
-
const remaining =
|
|
110
|
+
const remaining = offsets[next]! - elapsedTime()
|
|
103
111
|
if (active()) generator.prepare()
|
|
104
112
|
if (remaining > PREPARE_AHEAD) {
|
|
105
113
|
cancelPrepare = driver.after(remaining - PREPARE_AHEAD, () => {
|
|
@@ -116,6 +124,21 @@ export function playHaptics(
|
|
|
116
124
|
schedule()
|
|
117
125
|
return {
|
|
118
126
|
cancel,
|
|
127
|
+
pause() {
|
|
128
|
+
paused = true
|
|
129
|
+
cancelTimers()
|
|
130
|
+
},
|
|
131
|
+
play() {
|
|
132
|
+
if (paused) {
|
|
133
|
+
paused = false
|
|
134
|
+
schedule()
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
seek() {
|
|
138
|
+
cancelTimers()
|
|
139
|
+
while (next < offsets.length && offsets[next]! <= elapsedTime()) next++
|
|
140
|
+
schedule()
|
|
141
|
+
},
|
|
119
142
|
finish() {
|
|
120
143
|
// Native completion may beat the timer at the final keyframe by a frame.
|
|
121
144
|
fire(COMPLETION_TOLERANCE)
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { confetti, particles } from './particles.js'
|
|
|
7
7
|
export type { ConfettiOptions, ParticleOptions } from './particles.js'
|
|
8
8
|
export { shimmer } from './shimmer.js'
|
|
9
9
|
export type { ShimmerOptions } from './shimmer.js'
|
|
10
|
+
export { liquid } from './liquid.js'
|
|
11
|
+
export type { LiquidOptions, LiquidEffect } from './liquid.js'
|
|
10
12
|
export { loader } from './loader.js'
|
|
11
13
|
export type { LoaderOptions, LoaderStyle } from './loader.js'
|
|
12
14
|
export { reveal, conceal } from './reveal.js'
|
|
@@ -25,5 +27,7 @@ export { springs } from './transition.js'
|
|
|
25
27
|
export type { Spring, SpringName, Timing, Transition, Tween } from './transition.js'
|
|
26
28
|
export { cubicBezier } from './easing.js'
|
|
27
29
|
export type { CubicBezier, Easing, EasingFunction, EasingName } from './easing.js'
|
|
30
|
+
export type { PlaybackAnimation } from './playback.js'
|
|
28
31
|
export type { Animation } from './engine.js'
|
|
32
|
+
export type { ColorValue, ColorKeyframes } from './color.js'
|
|
29
33
|
export type * from './types.js'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** A periodic displacement field. The fixed boundary keeps transparent edges in bounds. */
|
|
2
|
+
export function liquidGrid(
|
|
3
|
+
columns: number,
|
|
4
|
+
rows: number,
|
|
5
|
+
phase: number,
|
|
6
|
+
intensity: number,
|
|
7
|
+
) {
|
|
8
|
+
const positions = new Float32Array((columns + 1) * (rows + 1) * 2)
|
|
9
|
+
const t = phase * Math.PI * 2
|
|
10
|
+
let index = 0
|
|
11
|
+
for (let row = 0; row <= rows; row++) {
|
|
12
|
+
const y = row / rows
|
|
13
|
+
for (let column = 0; column <= columns; column++) {
|
|
14
|
+
const x = column / columns
|
|
15
|
+
const edge = Math.sin(Math.PI * x) ** 2 * Math.sin(Math.PI * y) ** 2
|
|
16
|
+
const amount = 0.055 * intensity * edge
|
|
17
|
+
positions[index++] =
|
|
18
|
+
x +
|
|
19
|
+
amount *
|
|
20
|
+
(Math.sin(Math.PI * 2 * y + t) + 0.4 * Math.sin(Math.PI * 2 * (x + y) - 2 * t))
|
|
21
|
+
positions[index++] =
|
|
22
|
+
y +
|
|
23
|
+
amount *
|
|
24
|
+
(Math.cos(Math.PI * 2 * x - t) + 0.4 * Math.cos(Math.PI * 2 * (x - y) + 2 * t))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return positions
|
|
28
|
+
}
|