@rafaelaugusto/jolt 0.1.0-beta.12 → 0.1.0-beta.14
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 +9 -6
- package/dist/animate.d.ts +6 -4
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +3 -1
- package/dist/animate.js.map +1 -1
- package/dist/drag.js +3 -3
- package/dist/drag.js.map +1 -1
- package/dist/driver/android-gradient.d.ts.map +1 -1
- package/dist/driver/android-gradient.js +11 -1
- package/dist/driver/android-gradient.js.map +1 -1
- package/dist/driver/android-particles.d.ts +6 -0
- package/dist/driver/android-particles.d.ts.map +1 -0
- package/dist/driver/android-particles.js +93 -0
- package/dist/driver/android-particles.js.map +1 -0
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +2 -0
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/frame.d.ts +3 -0
- package/dist/driver/frame.d.ts.map +1 -0
- package/dist/driver/frame.js +7 -0
- package/dist/driver/frame.js.map +1 -0
- package/dist/driver/index.d.ts +2 -0
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-gradient.d.ts.map +1 -1
- package/dist/driver/ios-gradient.js +24 -2
- package/dist/driver/ios-gradient.js.map +1 -1
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +2 -0
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +2 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +32 -5
- package/dist/engine.js.map +1 -1
- package/dist/gradient-binding.d.ts +38 -0
- package/dist/gradient-binding.d.ts.map +1 -0
- package/dist/gradient-binding.js +128 -0
- package/dist/gradient-binding.js.map +1 -0
- package/dist/gradient.d.ts +19 -4
- package/dist/gradient.d.ts.map +1 -1
- package/dist/gradient.js +331 -70
- package/dist/gradient.js.map +1 -1
- package/dist/haptics.d.ts.map +1 -1
- package/dist/haptics.js +101 -28
- package/dist/haptics.js.map +1 -1
- package/dist/image-effect.d.ts +1 -0
- package/dist/image-effect.d.ts.map +1 -1
- package/dist/image-effect.js +10 -2
- package/dist/image-effect.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/motion-scope.d.ts +3 -2
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-scope.js +12 -2
- package/dist/motion-scope.js.map +1 -1
- package/dist/motion-value.d.ts +62 -8
- package/dist/motion-value.d.ts.map +1 -1
- package/dist/motion-value.js +327 -91
- package/dist/motion-value.js.map +1 -1
- package/dist/native/index.d.ts +7 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +4 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native-properties.d.ts +28 -0
- package/dist/native-properties.d.ts.map +1 -0
- package/dist/native-properties.js +154 -0
- package/dist/native-properties.js.map +1 -0
- package/dist/native-property-driver.d.ts +19 -0
- package/dist/native-property-driver.d.ts.map +1 -0
- package/dist/native-property-driver.js +69 -0
- package/dist/native-property-driver.js.map +1 -0
- package/dist/native-property.d.ts +35 -0
- package/dist/native-property.d.ts.map +1 -0
- package/dist/native-property.js +277 -0
- package/dist/native-property.js.map +1 -0
- package/dist/particles.d.ts +5 -2
- package/dist/particles.d.ts.map +1 -1
- package/dist/particles.js +16 -10
- package/dist/particles.js.map +1 -1
- package/dist/playback-group.d.ts.map +1 -1
- package/dist/playback-group.js +17 -1
- package/dist/playback-group.js.map +1 -1
- package/dist/playback.d.ts +15 -0
- package/dist/playback.d.ts.map +1 -1
- package/dist/playback.js +23 -0
- package/dist/playback.js.map +1 -1
- package/dist/scene-appearance.d.ts +14 -0
- package/dist/scene-appearance.d.ts.map +1 -0
- package/dist/scene-appearance.js +68 -0
- package/dist/scene-appearance.js.map +1 -0
- package/dist/sequence.d.ts +24 -0
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +181 -19
- package/dist/sequence.js.map +1 -1
- package/dist/timed-animation.d.ts +38 -0
- package/dist/timed-animation.d.ts.map +1 -0
- package/dist/timed-animation.js +177 -0
- package/dist/timed-animation.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/value-observers.d.ts +19 -0
- package/dist/value-observers.d.ts.map +1 -0
- package/dist/value-observers.js +111 -0
- package/dist/value-observers.js.map +1 -0
- package/dist/value-track.d.ts +13 -0
- package/dist/value-track.d.ts.map +1 -0
- package/dist/value-track.js +59 -0
- package/dist/value-track.js.map +1 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +28 -5
- package/dist/vue/index.js.map +1 -1
- package/package.json +6 -2
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +30 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/ParticleEmitter.java +192 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ParticleSimulation.java +114 -0
- package/platforms/android/proguard-rules.pro +2 -0
- package/src/android-plugin.d.ts +93 -0
- package/src/animate.ts +13 -7
- package/src/drag.ts +3 -3
- package/src/driver/android-gradient.ts +8 -1
- package/src/driver/android-particles.ts +118 -0
- package/src/driver/android.ts +2 -0
- package/src/driver/frame.ts +10 -0
- package/src/driver/index.ts +2 -0
- package/src/driver/ios-gradient.ts +24 -2
- package/src/driver/ios.ts +2 -0
- package/src/engine.ts +33 -6
- package/src/gradient-binding.ts +158 -0
- package/src/gradient.ts +376 -91
- package/src/haptics.ts +106 -28
- package/src/image-effect.ts +10 -2
- package/src/index.ts +6 -0
- package/src/ios-plugin.d.ts +40 -0
- package/src/motion-scope.ts +14 -3
- package/src/motion-value.ts +374 -99
- package/src/native/index.ts +23 -0
- package/src/native-properties.ts +188 -0
- package/src/native-property-driver.ts +123 -0
- package/src/native-property.ts +353 -0
- package/src/particles.ts +18 -17
- package/src/playback-group.ts +17 -1
- package/src/playback.ts +41 -0
- package/src/scene-appearance.ts +92 -0
- package/src/sequence.ts +209 -20
- package/src/timed-animation.ts +192 -0
- package/src/types.ts +1 -1
- package/src/value-observers.ts +133 -0
- package/src/value-track.ts +71 -0
- package/src/vue/index.ts +29 -5
package/src/gradient.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAndroid, isIOS, type View } from '@nativescript/core'
|
|
1
|
+
import { Application, Trace, isAndroid, isIOS, type View } from '@nativescript/core'
|
|
2
2
|
import { toView } from './animate.js'
|
|
3
3
|
import { colorMixer, parseColor, type ColorValue } from './color.js'
|
|
4
4
|
import { reducedMotion } from './config.js'
|
|
@@ -14,6 +14,19 @@ import {
|
|
|
14
14
|
type Transition,
|
|
15
15
|
} from './transition.js'
|
|
16
16
|
import type { Target } from './types.js'
|
|
17
|
+
import { createClock, type PlaybackClock } from './playback.js'
|
|
18
|
+
import {
|
|
19
|
+
cloneFrame,
|
|
20
|
+
gradientPatch,
|
|
21
|
+
gradientProgram,
|
|
22
|
+
put,
|
|
23
|
+
take,
|
|
24
|
+
type GradientBindings,
|
|
25
|
+
type GradientComponent,
|
|
26
|
+
type GradientPatch,
|
|
27
|
+
type GradientSource,
|
|
28
|
+
} from './gradient-binding.js'
|
|
29
|
+
export type { GradientBindings } from './gradient-binding.js'
|
|
17
30
|
|
|
18
31
|
export type GradientType = 'linear' | 'radial' | 'conic'
|
|
19
32
|
|
|
@@ -30,9 +43,9 @@ export interface GradientValues {
|
|
|
30
43
|
radius?: number
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
export interface GradientOptions extends
|
|
46
|
+
export interface GradientOptions extends GradientBindings {
|
|
34
47
|
type: GradientType
|
|
35
|
-
colors:
|
|
48
|
+
colors: NonNullable<GradientBindings['colors']>
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
export interface Gradient {
|
|
@@ -41,8 +54,16 @@ export interface Gradient {
|
|
|
41
54
|
animate(values: GradientValues, transition?: Transition | SpringName): Animation
|
|
42
55
|
/** Applies values at once, stopping the animation in progress. */
|
|
43
56
|
set(values: GradientValues): void
|
|
44
|
-
/**
|
|
57
|
+
/** Holds the current frame and detaches all gradient bindings; sources keep running. */
|
|
45
58
|
stop(): void
|
|
59
|
+
/** Attach mixed static/value components. Cleanup holds their current values; newer owners survive. */
|
|
60
|
+
bind(values: GradientBindings): () => void
|
|
61
|
+
/** Whether this gradient is explicitly suspended (independent of app/view lifecycle). */
|
|
62
|
+
readonly suspended: boolean
|
|
63
|
+
/** Hold and detach source subscriptions until resume; source playback is not paused. */
|
|
64
|
+
suspend(): void
|
|
65
|
+
/** Catch up to current source positions and reattach once. */
|
|
66
|
+
resume(): void
|
|
46
67
|
/** Takes the gradient off the view. */
|
|
47
68
|
remove(): void
|
|
48
69
|
}
|
|
@@ -72,7 +93,7 @@ export interface GradientPainter {
|
|
|
72
93
|
times: readonly number[],
|
|
73
94
|
timing: GradientTiming,
|
|
74
95
|
onEnd: (finished: boolean) => void,
|
|
75
|
-
): { cancel(): void }
|
|
96
|
+
): { cancel(): void; control?(elapsed: number, paused: boolean): void }
|
|
76
97
|
remove(): void
|
|
77
98
|
}
|
|
78
99
|
|
|
@@ -103,56 +124,211 @@ const types: readonly GradientType[] = ['linear', 'radial', 'conic']
|
|
|
103
124
|
*/
|
|
104
125
|
export function gradient(target: Target, options: GradientOptions): Gradient {
|
|
105
126
|
const view = toView(target)
|
|
127
|
+
const { frame, patch } = prepareGradient(options)
|
|
128
|
+
return new GradientController(view, options.type, frame, patch)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Validate declarative replacements before removing their current native layer. */
|
|
132
|
+
export function prepareGradient(options: GradientOptions) {
|
|
106
133
|
if (!options || !types.includes(options.type))
|
|
107
134
|
throw new TypeError(`Gradient type must be 'linear', 'radial' or 'conic'.`)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
if (!options.colors)
|
|
136
|
+
throw new RangeError('A gradient needs between two and eight colors.')
|
|
137
|
+
const base: GradientFrame = {
|
|
138
|
+
colors: [],
|
|
139
|
+
stops: [],
|
|
140
|
+
angle: 180,
|
|
141
|
+
x: 0.5,
|
|
142
|
+
y: 0.5,
|
|
143
|
+
radius: 0.5,
|
|
144
|
+
}
|
|
145
|
+
const patch = gradientPatch(options, 0)
|
|
146
|
+
const prepared = gradientProgram(
|
|
147
|
+
{ at: () => base, remaining: 0, elapsed: () => 0 },
|
|
148
|
+
patch,
|
|
112
149
|
)
|
|
113
|
-
return
|
|
150
|
+
return { frame: prepared.current, patch }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface GradientRun {
|
|
154
|
+
from: GradientFrame
|
|
155
|
+
to: GradientFrame
|
|
156
|
+
track: Track
|
|
157
|
+
keys: Set<GradientComponent>
|
|
158
|
+
masked: Set<GradientComponent>
|
|
159
|
+
clock: PlaybackClock
|
|
160
|
+
cancelEnd?: () => void
|
|
161
|
+
settle(finished: boolean): void
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
class GradientController implements Gradient {
|
|
117
165
|
private painter: GradientPainter | undefined
|
|
118
|
-
private
|
|
119
|
-
private
|
|
166
|
+
private player: ReturnType<GradientPainter['play']> | undefined
|
|
167
|
+
private program: ReturnType<typeof gradientProgram> | undefined
|
|
120
168
|
private model: GradientFrame
|
|
121
|
-
private
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
private removed = false
|
|
170
|
+
private _suspended = false
|
|
171
|
+
private background = false
|
|
172
|
+
private unloadedView = false
|
|
173
|
+
private running: GradientRun | undefined
|
|
174
|
+
private bindings = new Map<
|
|
175
|
+
GradientComponent,
|
|
176
|
+
{ source: GradientSource; owner: object }
|
|
177
|
+
>()
|
|
178
|
+
private releases: (() => void)[] = []
|
|
179
|
+
private cancelWindow?: () => void
|
|
180
|
+
private generation = 0
|
|
131
181
|
|
|
132
182
|
constructor(
|
|
133
183
|
private readonly view: View,
|
|
134
184
|
readonly type: GradientType,
|
|
135
185
|
frame: GradientFrame,
|
|
186
|
+
patch: GradientPatch,
|
|
136
187
|
) {
|
|
137
188
|
this.model = frame
|
|
138
|
-
|
|
189
|
+
const owner = {}
|
|
190
|
+
for (const [key, source] of patch)
|
|
191
|
+
if (typeof source !== 'number') this.bindings.set(key, { source, owner })
|
|
192
|
+
this.painter = (factory ?? platformPainter)(view, type)
|
|
139
193
|
view.on('unloaded', this.unloaded)
|
|
194
|
+
view.on('loaded', this.loaded)
|
|
195
|
+
view.on('layoutChanged', this.resized)
|
|
196
|
+
Application.on(Application.suspendEvent, this.appSuspended)
|
|
197
|
+
Application.on(Application.resumeEvent, this.appResumed)
|
|
198
|
+
this.subscribe()
|
|
199
|
+
this.render()
|
|
140
200
|
}
|
|
141
201
|
|
|
142
|
-
private
|
|
143
|
-
|
|
144
|
-
|
|
202
|
+
private get inactive() {
|
|
203
|
+
return this.removed || this._suspended || this.background || this.unloadedView
|
|
204
|
+
}
|
|
205
|
+
private patch() {
|
|
206
|
+
return new Map([...this.bindings].map(([key, entry]) => [key, entry.source]))
|
|
207
|
+
}
|
|
208
|
+
private base() {
|
|
209
|
+
const run = this.running
|
|
210
|
+
const elapsed = run?.clock.time() ?? 0
|
|
211
|
+
const model = cloneFrame(this.model)
|
|
212
|
+
return {
|
|
213
|
+
remaining: run ? Math.max(0, run.track.end - elapsed) : 0,
|
|
214
|
+
elapsed: () => (run ? Math.max(0, run.clock.time() - elapsed) : 0),
|
|
215
|
+
at: (delta: number) => {
|
|
216
|
+
const frame = cloneFrame(model)
|
|
217
|
+
if (run) {
|
|
218
|
+
const sample = mix(
|
|
219
|
+
run.from,
|
|
220
|
+
run.to,
|
|
221
|
+
run.track.valueAt(elapsed + delta),
|
|
222
|
+
reducedMotion(),
|
|
223
|
+
)
|
|
224
|
+
for (const key of run.keys)
|
|
225
|
+
if (!run.masked.has(key)) put(frame, key, take(sample, key))
|
|
226
|
+
}
|
|
227
|
+
return frame
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
private presented(): GradientFrame {
|
|
232
|
+
if (this.inactive) return cloneFrame(this.model)
|
|
233
|
+
try {
|
|
234
|
+
return gradientProgram(this.base(), this.patch()).current
|
|
235
|
+
} catch {
|
|
236
|
+
return (
|
|
237
|
+
this.program?.at(Math.min(this.program.duration, this.program.elapsed())) ??
|
|
238
|
+
cloneFrame(this.model)
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
private clearPlayer() {
|
|
243
|
+
this.generation++
|
|
244
|
+
this.cancelWindow?.()
|
|
245
|
+
this.cancelWindow = undefined
|
|
246
|
+
const player = this.player
|
|
247
|
+
this.player = undefined
|
|
248
|
+
player?.cancel()
|
|
249
|
+
}
|
|
250
|
+
private unsubscribe() {
|
|
251
|
+
for (const release of this.releases) release()
|
|
252
|
+
this.releases = []
|
|
253
|
+
}
|
|
254
|
+
private subscribe() {
|
|
255
|
+
this.unsubscribe()
|
|
256
|
+
if (this.inactive) return
|
|
257
|
+
const sources = new Map<object, GradientSource>()
|
|
258
|
+
for (const { source } of this.bindings.values()) sources.set(source.identity, source)
|
|
259
|
+
this.releases = [...sources.values()].map((source) => source.subscribe(this.changed))
|
|
260
|
+
}
|
|
261
|
+
private changed = () => {
|
|
262
|
+
if (this.inactive) return
|
|
263
|
+
try {
|
|
264
|
+
this.render()
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// A later invalid source must not throw through its other consumers or leave
|
|
267
|
+
// half of a compound frame painted. Hold the last compiled frame and detach.
|
|
268
|
+
this.stop()
|
|
269
|
+
Trace.write(
|
|
270
|
+
`Jolt gradient binding stopped: ${String(error)}`,
|
|
271
|
+
Trace.categories.Error,
|
|
272
|
+
Trace.messageType.error,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
private render() {
|
|
277
|
+
if (this.inactive) return
|
|
278
|
+
const prepared = gradientProgram(this.base(), this.patch())
|
|
279
|
+
this.clearPlayer()
|
|
280
|
+
this.program = prepared
|
|
281
|
+
const painter = this.painter
|
|
145
282
|
if (!painter) return
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
283
|
+
const run = this.running
|
|
284
|
+
if (run && !this.bindings.size && !run.masked.size) {
|
|
285
|
+
const generation = this.generation
|
|
286
|
+
this.player = painter.play(
|
|
287
|
+
run.track.values.map((p) => mix(run.from, run.to, p, reducedMotion())),
|
|
288
|
+
run.track.times,
|
|
289
|
+
{
|
|
290
|
+
duration: run.track.duration,
|
|
291
|
+
delay: run.track.delay,
|
|
292
|
+
repeat: run.track.repeat,
|
|
293
|
+
reverse: run.track.reverse,
|
|
294
|
+
},
|
|
295
|
+
(completed) => {
|
|
296
|
+
if (generation === this.generation && this.running === run)
|
|
297
|
+
this.finish(run, completed)
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
// Concrete playback retains its existing API; bindings always use native control.
|
|
301
|
+
if (run.clock.time() > 1) this.player.control?.(run.clock.time(), false)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
if (prepared.duration <= 0) {
|
|
305
|
+
painter.paint(prepared.current)
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
const generation = this.generation
|
|
309
|
+
this.player = painter.play(
|
|
310
|
+
prepared.frames,
|
|
311
|
+
prepared.times,
|
|
312
|
+
{ duration: prepared.duration, delay: 0, repeat: 0, reverse: false },
|
|
313
|
+
() => {},
|
|
314
|
+
)
|
|
315
|
+
this.player.control?.(prepared.elapsed(), false)
|
|
316
|
+
// Only long compound windows need a boundary callback; each source sends its
|
|
317
|
+
// own completion/control notifications. There is no observer frame subscription.
|
|
318
|
+
if (prepared.remaining > prepared.duration)
|
|
319
|
+
this.cancelWindow = getDriver().after(
|
|
320
|
+
Math.max(1, prepared.duration - prepared.elapsed()),
|
|
321
|
+
() => {
|
|
322
|
+
if (generation === this.generation) this.changed()
|
|
323
|
+
},
|
|
324
|
+
)
|
|
150
325
|
}
|
|
151
326
|
|
|
152
327
|
animate(values: GradientValues, transition?: Transition | SpringName): Animation {
|
|
153
328
|
this.assertActive()
|
|
154
329
|
const from = this.presented()
|
|
155
330
|
const to = resolve(from, values, true)
|
|
331
|
+
const patch = gradientPatch(values, from.colors.length)
|
|
156
332
|
const resolved =
|
|
157
333
|
typeof transition === 'string' ? springOf(transition) : (transition ?? {})
|
|
158
334
|
const track = createTrack(
|
|
@@ -161,96 +337,205 @@ class GradientController implements Gradient {
|
|
|
161
337
|
resolveTransition(resolved, 'opacity', 2),
|
|
162
338
|
0,
|
|
163
339
|
)
|
|
164
|
-
const reduced = reducedMotion()
|
|
165
|
-
const frameAt = (progress: number) => mix(from, to, progress, reduced)
|
|
166
340
|
this.cancelRunning()
|
|
167
|
-
this.model =
|
|
168
|
-
|
|
341
|
+
this.model = from
|
|
342
|
+
for (const key of patch.keys()) this.bindings.delete(key)
|
|
343
|
+
this.subscribe()
|
|
169
344
|
let settle!: (finished: boolean) => void
|
|
170
|
-
const finished = new Promise<boolean>((resolve) =>
|
|
345
|
+
const finished = new Promise<boolean>((resolve) => {
|
|
346
|
+
settle = resolve
|
|
347
|
+
})
|
|
348
|
+
const driver = getDriver()
|
|
349
|
+
const run: GradientRun = {
|
|
350
|
+
from,
|
|
351
|
+
to,
|
|
352
|
+
track,
|
|
353
|
+
keys: new Set(patch.keys()),
|
|
354
|
+
masked: new Set(),
|
|
355
|
+
clock: driver.clock?.(track.end) ?? createClock(() => driver.now()),
|
|
356
|
+
settle,
|
|
357
|
+
}
|
|
171
358
|
const animation: Animation = {
|
|
172
359
|
finished,
|
|
173
360
|
stop: () => {
|
|
174
|
-
if (this.running
|
|
361
|
+
if (this.running !== run) return
|
|
362
|
+
this.model = this.presented()
|
|
363
|
+
this.cancelRunning()
|
|
364
|
+
this.render()
|
|
175
365
|
},
|
|
176
366
|
then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
|
|
177
367
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
368
|
+
this.running = run
|
|
369
|
+
if (track.end === 0 || !this.painter) this.finish(run, true)
|
|
370
|
+
else {
|
|
371
|
+
this.render()
|
|
372
|
+
if (Number.isFinite(track.end))
|
|
373
|
+
run.cancelEnd = driver.after(Math.max(1, track.end - run.clock.time()), () =>
|
|
374
|
+
this.finish(run, true),
|
|
375
|
+
)
|
|
182
376
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
377
|
+
return animation
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private finish(run: GradientRun, completed: boolean) {
|
|
381
|
+
if (this.running !== run) return
|
|
382
|
+
const frame = completed
|
|
383
|
+
? mix(run.from, run.to, run.track.to, reducedMotion())
|
|
384
|
+
: this.presented()
|
|
385
|
+
for (const key of run.keys)
|
|
386
|
+
if (!run.masked.has(key)) put(this.model, key, take(frame, key))
|
|
387
|
+
this.running = undefined
|
|
388
|
+
run.cancelEnd?.()
|
|
389
|
+
run.clock.cancel()
|
|
390
|
+
this.clearPlayer()
|
|
391
|
+
run.settle(completed)
|
|
392
|
+
this.render()
|
|
393
|
+
}
|
|
394
|
+
private cancelRunning() {
|
|
395
|
+
const run = this.running
|
|
396
|
+
this.running = undefined
|
|
397
|
+
run?.cancelEnd?.()
|
|
398
|
+
run?.clock.cancel()
|
|
399
|
+
this.clearPlayer()
|
|
400
|
+
run?.settle(false)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
bind(values: GradientBindings): () => void {
|
|
404
|
+
this.assertActive()
|
|
405
|
+
const held = this.presented()
|
|
406
|
+
const patch = gradientPatch(values, held.colors.length)
|
|
407
|
+
const candidate: GradientPatch = this.patch()
|
|
408
|
+
const count = values.colors?.length ?? held.colors.length
|
|
409
|
+
const structural = count !== held.colors.length
|
|
410
|
+
if (structural) {
|
|
411
|
+
held.colors.length = held.stops.length = count
|
|
412
|
+
for (const key of candidate.keys())
|
|
413
|
+
if (key.startsWith('color:') || key.startsWith('stop:')) candidate.delete(key)
|
|
414
|
+
}
|
|
415
|
+
for (const [key, value] of patch) candidate.set(key, value)
|
|
416
|
+
const prepared = gradientProgram(
|
|
417
|
+
{ at: () => held, remaining: 0, elapsed: () => 0 },
|
|
418
|
+
candidate,
|
|
197
419
|
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
420
|
+
// Validation/sampling above is transactional. Only now may ownership change.
|
|
421
|
+
const owner = {}
|
|
422
|
+
if (structural) {
|
|
423
|
+
for (const key of this.bindings.keys())
|
|
424
|
+
if (key.startsWith('color:') || key.startsWith('stop:')) this.bindings.delete(key)
|
|
425
|
+
for (const key of this.running?.keys ?? [])
|
|
426
|
+
if (key.startsWith('color:') || key.startsWith('stop:'))
|
|
427
|
+
this.running!.masked.add(key)
|
|
428
|
+
}
|
|
429
|
+
this.model = prepared.current
|
|
430
|
+
for (const [key, source] of patch) {
|
|
431
|
+
this.running?.masked.add(key)
|
|
432
|
+
if (typeof source === 'number') this.bindings.delete(key)
|
|
433
|
+
else this.bindings.set(key, { source, owner })
|
|
434
|
+
}
|
|
435
|
+
this.subscribe()
|
|
436
|
+
this.render()
|
|
437
|
+
return () => {
|
|
438
|
+
if (this.removed) return
|
|
439
|
+
const keys = [...this.bindings]
|
|
440
|
+
.filter(([, entry]) => entry.owner === owner)
|
|
441
|
+
.map(([key]) => key)
|
|
442
|
+
if (!keys.length) return
|
|
443
|
+
const current = this.presented()
|
|
444
|
+
for (const key of keys) {
|
|
445
|
+
put(this.model, key, take(current, key))
|
|
446
|
+
this.bindings.delete(key)
|
|
447
|
+
}
|
|
448
|
+
this.subscribe()
|
|
449
|
+
this.render()
|
|
205
450
|
}
|
|
206
|
-
return animation
|
|
207
451
|
}
|
|
208
452
|
|
|
209
453
|
set(values: GradientValues) {
|
|
210
454
|
this.assertActive()
|
|
211
|
-
const
|
|
455
|
+
const from = this.presented()
|
|
456
|
+
resolve(from, values, false)
|
|
457
|
+
gradientPatch(values, from.colors.length)
|
|
212
458
|
this.cancelRunning()
|
|
213
|
-
this.model =
|
|
214
|
-
this.
|
|
459
|
+
this.model = from
|
|
460
|
+
this.bind(values)
|
|
215
461
|
}
|
|
216
|
-
|
|
217
462
|
stop() {
|
|
218
|
-
if (
|
|
219
|
-
const
|
|
463
|
+
if (this.removed) return
|
|
464
|
+
const frame = this.presented()
|
|
465
|
+
this.bindings.clear()
|
|
466
|
+
this.unsubscribe()
|
|
467
|
+
this.cancelRunning()
|
|
468
|
+
this.model = frame
|
|
469
|
+
this.program = undefined
|
|
470
|
+
this.painter?.paint(frame)
|
|
471
|
+
}
|
|
472
|
+
private hold() {
|
|
473
|
+
this.model = this.presented()
|
|
220
474
|
this.cancelRunning()
|
|
221
|
-
this.
|
|
222
|
-
this.paint(
|
|
475
|
+
this.unsubscribe()
|
|
476
|
+
this.painter?.paint(this.model)
|
|
477
|
+
}
|
|
478
|
+
get suspended() {
|
|
479
|
+
return this._suspended
|
|
223
480
|
}
|
|
224
481
|
|
|
482
|
+
suspend() {
|
|
483
|
+
if (!this._suspended && !this.removed) {
|
|
484
|
+
this.hold()
|
|
485
|
+
this._suspended = true
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
resume() {
|
|
489
|
+
if (this._suspended && !this.removed) {
|
|
490
|
+
this._suspended = false
|
|
491
|
+
this.subscribe()
|
|
492
|
+
this.changed()
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
private unloaded = () => {
|
|
496
|
+
if (this.removed) return
|
|
497
|
+
this.hold()
|
|
498
|
+
this.unloadedView = true
|
|
499
|
+
this.painter?.remove()
|
|
500
|
+
this.painter = undefined
|
|
501
|
+
}
|
|
502
|
+
private loaded = () => {
|
|
503
|
+
if (this.removed) return
|
|
504
|
+
this.unloadedView = false
|
|
505
|
+
this.painter ??= (factory ?? platformPainter)(this.view, this.type)
|
|
506
|
+
this.subscribe()
|
|
507
|
+
this.changed()
|
|
508
|
+
}
|
|
509
|
+
private resized = () => {
|
|
510
|
+
if (this.bindings.size || this.running) this.changed()
|
|
511
|
+
}
|
|
512
|
+
private appSuspended = () => {
|
|
513
|
+
if (!this.background && !this.removed) {
|
|
514
|
+
this.hold()
|
|
515
|
+
this.background = true
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
private appResumed = () => {
|
|
519
|
+
if (this.background && !this.removed) {
|
|
520
|
+
this.background = false
|
|
521
|
+
this.subscribe()
|
|
522
|
+
this.changed()
|
|
523
|
+
}
|
|
524
|
+
}
|
|
225
525
|
remove() {
|
|
226
526
|
if (this.removed) return
|
|
527
|
+
this.unsubscribe()
|
|
227
528
|
this.cancelRunning()
|
|
529
|
+
this.bindings.clear()
|
|
228
530
|
this.removed = true
|
|
229
531
|
this.view.off('unloaded', this.unloaded)
|
|
532
|
+
this.view.off('loaded', this.loaded)
|
|
533
|
+
this.view.off('layoutChanged', this.resized)
|
|
534
|
+
Application.off(Application.suspendEvent, this.appSuspended)
|
|
535
|
+
Application.off(Application.resumeEvent, this.appResumed)
|
|
230
536
|
this.painter?.remove()
|
|
231
537
|
this.painter = undefined
|
|
232
538
|
}
|
|
233
|
-
|
|
234
|
-
private paint(frame: GradientFrame) {
|
|
235
|
-
if (this.painter) this.painter.paint(frame)
|
|
236
|
-
else this.pending = frame
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private presented(): GradientFrame {
|
|
240
|
-
const running = this.running
|
|
241
|
-
if (!running) return this.model
|
|
242
|
-
const elapsed = getDriver().now() - running.startedAt
|
|
243
|
-
return mix(running.from, running.to, running.track.valueAt(elapsed), reducedMotion())
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
private cancelRunning() {
|
|
247
|
-
const running = this.running
|
|
248
|
-
if (!running) return
|
|
249
|
-
this.running = undefined
|
|
250
|
-
running.handle.cancel()
|
|
251
|
-
running.settle(false)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
539
|
private assertActive() {
|
|
255
540
|
if (this.removed) throw new Error('This gradient was removed.')
|
|
256
541
|
}
|