|
@@ -88,6 +88,9 @@ export class PointerTracker implements Tracker
|
88
|
88
|
/** time of the previous update */
|
89
|
89
|
private _previousUpdateTime: DOMHighResTimeStamp;
|
90
|
90
|
|
|
91
|
+ /** helper flag */
|
|
92
|
+ private _wantToReset: boolean;
|
|
93
|
+
|
91
|
94
|
|
92
|
95
|
|
93
|
96
|
/**
|
|
@@ -101,6 +104,8 @@ export class PointerTracker implements Tracker
|
101
|
104
|
this._newPointers = new Map();
|
102
|
105
|
this._previousOutput = this._generateOutput();
|
103
|
106
|
this._previousUpdateTime = Number.POSITIVE_INFINITY;
|
|
107
|
+ this._wantToReset = false;
|
|
108
|
+ this._resetInTheNextUpdate = this._resetInTheNextUpdate.bind(this);
|
104
|
109
|
}
|
105
|
110
|
|
106
|
111
|
/**
|
|
@@ -138,6 +143,9 @@ export class PointerTracker implements Tracker
|
138
|
143
|
// link the pointer source to the viewport
|
139
|
144
|
this._source._setViewport(this._viewport);
|
140
|
145
|
|
|
146
|
+ // reset trackables
|
|
147
|
+ document.addEventListener('visibilitychange', this._resetInTheNextUpdate);
|
|
148
|
+
|
141
|
149
|
// done!
|
142
|
150
|
return Speedy.Promise.resolve();
|
143
|
151
|
}
|
|
@@ -154,6 +162,8 @@ export class PointerTracker implements Tracker
|
154
|
162
|
this._activePointers.clear();
|
155
|
163
|
this._newPointers.clear();
|
156
|
164
|
|
|
165
|
+ document.removeEventListener('visibilitychange', this._resetInTheNextUpdate);
|
|
166
|
+
|
157
|
167
|
return Speedy.Promise.resolve();
|
158
|
168
|
}
|
159
|
169
|
|
|
@@ -172,17 +182,23 @@ export class PointerTracker implements Tracker
|
172
|
182
|
const inverseDeltaTime = (deltaTime > 1e-5) ? 1 / deltaTime : 60; // 1/dt = 1 / (1/60) with 60 fps
|
173
|
183
|
|
174
|
184
|
// remove inactive trackables from the previous frame (update cycle)
|
175
|
|
- const inactiveTrackables = this._findUnwantedTrackables();
|
|
185
|
+ const inactiveTrackables = this._findInactiveTrackables();
|
176
|
186
|
for(let i = inactiveTrackables.length - 1; i >= 0; i--)
|
177
|
187
|
this._activePointers.delete(inactiveTrackables[i].id);
|
178
|
188
|
|
179
|
189
|
// make all active trackables stationary
|
180
|
|
- this._activePointers.forEach((trackable, id) => {
|
181
|
|
- this._activePointers.set(id, Object.assign({}, trackable, {
|
182
|
|
- phase: 'stationary'
|
183
|
|
- }));
|
|
190
|
+ this._updateAllTrackables({
|
|
191
|
+ phase: 'stationary',
|
|
192
|
+ velocity: Vector2.Zero(),
|
|
193
|
+ deltaPosition: Vector2.Zero()
|
184
|
194
|
});
|
185
|
195
|
|
|
196
|
+ // want to reset?
|
|
197
|
+ if(this._wantToReset) {
|
|
198
|
+ this._reset();
|
|
199
|
+ this._wantToReset = false;
|
|
200
|
+ }
|
|
201
|
+
|
186
|
202
|
// consume events
|
187
|
203
|
let event: Nullable<PointerEvent>;
|
188
|
204
|
while((event = this._source!._consume()) !== null) {
|
|
@@ -238,6 +254,10 @@ export class PointerTracker implements Tracker
|
238
|
254
|
}
|
239
|
255
|
}
|
240
|
256
|
|
|
257
|
+ // discard previously canceled pointers (e.g., with a visibilitychange event)
|
|
258
|
+ if(previous?.phase == 'canceled')
|
|
259
|
+ continue;
|
|
260
|
+
|
241
|
261
|
// more special rules
|
242
|
262
|
switch(event.type) {
|
243
|
263
|
case 'pointermove':
|
|
@@ -249,6 +269,11 @@ export class PointerTracker implements Tracker
|
249
|
269
|
if(event.buttons == 0 || previous?.phase == 'began' || current?.phase == 'began')
|
250
|
270
|
continue;
|
251
|
271
|
break;
|
|
272
|
+
|
|
273
|
+ case 'pointercancel': // purge everything
|
|
274
|
+ this._reset();
|
|
275
|
+ this._newPointers.clear();
|
|
276
|
+ continue;
|
252
|
277
|
}
|
253
|
278
|
|
254
|
279
|
// determine the current position
|
|
@@ -287,6 +312,7 @@ export class PointerTracker implements Tracker
|
287
|
312
|
// update trackables
|
288
|
313
|
this._newPointers.forEach((trackable, id) => this._activePointers.set(id, trackable));
|
289
|
314
|
this._newPointers.clear();
|
|
315
|
+ this._advanceAllStationaryTrackables(deltaTime);
|
290
|
316
|
|
291
|
317
|
// generate output
|
292
|
318
|
this._previousOutput = this._generateOutput();
|
|
@@ -337,6 +363,60 @@ export class PointerTracker implements Tracker
|
337
|
363
|
}
|
338
|
364
|
|
339
|
365
|
/**
|
|
366
|
+ * Update all active pointers
|
|
367
|
+ * @param fields
|
|
368
|
+ */
|
|
369
|
+ private _updateAllTrackables(fields: Partial<TrackablePointer>): void
|
|
370
|
+ {
|
|
371
|
+ this._activePointers.forEach((trackable, id) => {
|
|
372
|
+ this._activePointers.set(id, Object.assign({}, trackable, fields));
|
|
373
|
+ });
|
|
374
|
+ }
|
|
375
|
+
|
|
376
|
+ /**
|
|
377
|
+ * Advance the elapsed time of all stationary pointers
|
|
378
|
+ * @param deltaTime
|
|
379
|
+ */
|
|
380
|
+ private _advanceAllStationaryTrackables(deltaTime: number): void
|
|
381
|
+ {
|
|
382
|
+ this._activePointers.forEach((trackable, id) => {
|
|
383
|
+ if(trackable.phase == 'stationary') {
|
|
384
|
+ (trackable as any).elapsedTime += deltaTime;
|
|
385
|
+ /*
|
|
386
|
+ this._activePointers.set(id, Object.assign({}, trackable, {
|
|
387
|
+ elapsedTime: trackable.elapsedTime + deltaTime
|
|
388
|
+ }));
|
|
389
|
+ */
|
|
390
|
+ }
|
|
391
|
+ });
|
|
392
|
+ }
|
|
393
|
+
|
|
394
|
+ /**
|
|
395
|
+ * Cancel all active pointers and consume all events
|
|
396
|
+ * @param deltaTime
|
|
397
|
+ */
|
|
398
|
+ private _reset(): void
|
|
399
|
+ {
|
|
400
|
+ // cancel all active pointers
|
|
401
|
+ this._updateAllTrackables({
|
|
402
|
+ phase: 'canceled',
|
|
403
|
+ deltaPosition: Vector2.Zero(),
|
|
404
|
+ velocity: Vector2.Zero(),
|
|
405
|
+ });
|
|
406
|
+
|
|
407
|
+ // consume all events
|
|
408
|
+ while(this._source!._consume() !== null);
|
|
409
|
+ }
|
|
410
|
+
|
|
411
|
+ /**
|
|
412
|
+ * Reset in the next update of the tracker
|
|
413
|
+ */
|
|
414
|
+ private _resetInTheNextUpdate(): void
|
|
415
|
+ {
|
|
416
|
+ this._wantToReset = true;
|
|
417
|
+ }
|
|
418
|
+
|
|
419
|
+ /**
|
340
|
420
|
* As a convenience, let's make sure that a primary pointer, if any exists,
|
341
|
421
|
* is at the beginning of the trackables array
|
342
|
422
|
* @param trackables
|
|
@@ -344,6 +424,20 @@ export class PointerTracker implements Tracker
|
344
|
424
|
*/
|
345
|
425
|
private _sortTrackables(trackables: TrackablePointer[]): TrackablePointer[]
|
346
|
426
|
{
|
|
427
|
+ /*
|
|
428
|
+
|
|
429
|
+ Note: the browser may not report a new unique pointer (phase: "began")
|
|
430
|
+ as primary. This logic makes trackables[0] primary, or sort of primary.
|
|
431
|
+
|
|
432
|
+ Behavior on Chrome 130 on Android: when moving multiple touch points,
|
|
433
|
+ remove focus from the browser. Touch points will be canceled as
|
|
434
|
+ expected. When touching the screen again with a single finger, the
|
|
435
|
+ (only one) registered pointer will not be primary. That's undesirable.
|
|
436
|
+ Touching the screen again with multiple fingers (none will be primary),
|
|
437
|
+ and then releasing them, will restore the desired behavior.
|
|
438
|
+
|
|
439
|
+ */
|
|
440
|
+
|
347
|
441
|
// nothing to do
|
348
|
442
|
if(trackables.length <= 1 || trackables[0].isPrimary)
|
349
|
443
|
return trackables;
|
|
@@ -366,7 +460,7 @@ export class PointerTracker implements Tracker
|
366
|
460
|
* Find trackables to remove
|
367
|
461
|
* @returns a list of trackables to remove
|
368
|
462
|
*/
|
369
|
|
- private _findUnwantedTrackables(): TrackablePointer[]
|
|
463
|
+ private _findInactiveTrackables(): TrackablePointer[]
|
370
|
464
|
{
|
371
|
465
|
const trackables: TrackablePointer[] = [];
|
372
|
466
|
|