mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
fix(awareness): follow a gesture to its last contact, and drop it on blur
Lifting one finger of a pinch removed the document listeners, so the remaining finger's drag, which Cesium carries on with, stopped counting. A gesture now ends only when its last pointer lifts or no touches are left. And a gesture whose end never reaches the page (the window loses focus mid-drag, or the page is hidden) left the listeners attached, so a later unrelated drag counted as camera input; a blur or hidden page now stops following it and drops its pending input.
This commit is contained in:
1 parent
de634952ec
commit
02a6739466
3 files changed
+105
-1
No files matched your search
@@ -23,6 +23,7 @@ export function createLifecycle({
|
||||
if (event?.type === 'pointermove' && !event.buttons) return;
|
||||
if (!cameraInputsEnabled()) return;
|
||||
layerState.lastCameraInputMs = Date.now();
|
||||
if (event?.type === 'pointerdown') activePointers.add(event.pointerId);
|
||||
if (event?.type === 'pointerdown' || event?.type === 'touchstart')
|
||||
followGesture(true);
|
||||
}
|
||||
@@ -39,22 +40,42 @@ export function createLifecycle({
|
||||
'touchcancel',
|
||||
];
|
||||
let followingGesture = false;
|
||||
const activePointers = new Set();
|
||||
|
||||
function followGesture(on) {
|
||||
if (followingGesture === on) return;
|
||||
followingGesture = on;
|
||||
if (!on) activePointers.clear();
|
||||
const listen = on ? 'addEventListener' : 'removeEventListener';
|
||||
for (const type of GESTURE_MOVE_EVENTS)
|
||||
globalThis.document?.[listen]?.(type, noteCameraInput, { passive: true });
|
||||
for (const type of GESTURE_END_EVENTS)
|
||||
globalThis.document?.[listen]?.(type, endGesture, { passive: true });
|
||||
globalThis[listen]?.('blur', dropGesture);
|
||||
globalThis.document?.[listen]?.('visibilitychange', dropGesture);
|
||||
}
|
||||
|
||||
// A gesture ends with its last contact: lifting one finger of a pinch
|
||||
// leaves Cesium a one-finger drag.
|
||||
function endGesture(event) {
|
||||
noteCameraInput(event);
|
||||
activePointers.delete(event?.pointerId);
|
||||
if (activePointers.size || event?.touches?.length) return;
|
||||
followGesture(false);
|
||||
}
|
||||
|
||||
// Losing the window or the page mid-gesture means its end may never
|
||||
// arrive here, so stop following it and drop its pending input.
|
||||
function dropGesture(event) {
|
||||
if (
|
||||
event?.type === 'visibilitychange' &&
|
||||
document.visibilityState !== 'hidden'
|
||||
)
|
||||
return;
|
||||
followGesture(false);
|
||||
layerState.lastCameraInputMs = Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
function cameraInputsEnabled() {
|
||||
const controller = layerState.viewer?.scene?.screenSpaceCameraController;
|
||||
return controller?.enableInputs !== false;
|
||||
|
||||
@@ -706,6 +706,89 @@ test('a long drag or touch keeps the pulses awake after it leaves the canvas', (
|
||||
assert.equal(ring.state.lastCameraInputMs, noted);
|
||||
});
|
||||
|
||||
test('lifting one finger of a pinch leaves the other finger counting', (t) => {
|
||||
const ring = ringRendering(t);
|
||||
const { inputWakeMs } = AWARENESS_RANGE_PULSE;
|
||||
const { dispatch, pan, pulsing, loop } = inputLifecycle(t, ring);
|
||||
const onDocument = (type, init = {}) =>
|
||||
document.dispatchEvent(Object.assign(new Event(type), init));
|
||||
// Cesium carries on with a one-finger drag after the other lifts.
|
||||
const dragOn = (move, init) => {
|
||||
for (let ms = 0; ms < inputWakeMs + 1000; ms += 250) {
|
||||
loop.advance(250);
|
||||
onDocument(move, init);
|
||||
}
|
||||
pan();
|
||||
return pulsing();
|
||||
};
|
||||
// Without pointer events: the touch list says who is still down.
|
||||
dispatch('touchstart');
|
||||
onDocument('touchend', { touches: [{}] });
|
||||
assert.equal(dragOn('touchmove', {}), true, 'touch: one finger left');
|
||||
onDocument('touchend', { touches: [] });
|
||||
assert.equal(dragOn('touchmove', {}), false, 'touch: all lifted');
|
||||
// With pointer events: each contact has its own pointer id.
|
||||
dispatch('pointerdown', { pointerId: 1 });
|
||||
dispatch('pointerdown', { pointerId: 2 });
|
||||
onDocument('pointerup', { pointerId: 1 });
|
||||
const drag = { pointerId: 2, buttons: 1 };
|
||||
assert.equal(dragOn('pointermove', drag), true, 'pointer: one left');
|
||||
onDocument('pointercancel', { pointerId: 2 });
|
||||
assert.equal(dragOn('pointermove', drag), false, 'pointer: all lifted');
|
||||
});
|
||||
|
||||
test('a gesture lost to a window blur or a hidden page stops counting', (t) => {
|
||||
const ring = ringRendering(t);
|
||||
const { inputWakeMs } = AWARENESS_RANGE_PULSE;
|
||||
const { dispatch, pan, pulsing, loop } = inputLifecycle(t, ring);
|
||||
const visibility = Object.getOwnPropertyDescriptor(
|
||||
document,
|
||||
'visibilityState',
|
||||
);
|
||||
t.after(() => {
|
||||
if (visibility)
|
||||
Object.defineProperty(document, 'visibilityState', visibility);
|
||||
else delete document.visibilityState;
|
||||
});
|
||||
const setVisibility = (value) =>
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
get: () => value,
|
||||
});
|
||||
for (const [name, lose] of [
|
||||
['blur', () => window.dispatchEvent(new Event('blur'))],
|
||||
[
|
||||
'hidden',
|
||||
() => {
|
||||
setVisibility('hidden');
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
setVisibility('visible');
|
||||
},
|
||||
],
|
||||
]) {
|
||||
// The release happens outside the window, so no end ever arrives.
|
||||
dispatch('pointerdown', { pointerId: 1 });
|
||||
lose();
|
||||
pan();
|
||||
assert.equal(pulsing(), false, `${name}: pending input dropped`);
|
||||
loop.advance(inputWakeMs + 100);
|
||||
const noted = ring.state.lastCameraInputMs;
|
||||
document.dispatchEvent(
|
||||
Object.assign(new Event('pointermove'), { pointerId: 1, buttons: 1 }),
|
||||
);
|
||||
assert.equal(ring.state.lastCameraInputMs, noted, `${name}: no follow`);
|
||||
}
|
||||
// A page coming back into view is no reason to drop a gesture.
|
||||
dispatch('pointerdown', { pointerId: 1 });
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
loop.advance(inputWakeMs + 100);
|
||||
document.dispatchEvent(
|
||||
Object.assign(new Event('pointermove'), { pointerId: 1, buttons: 1 }),
|
||||
);
|
||||
pan();
|
||||
assert.equal(pulsing(), true, 'visible: still following');
|
||||
});
|
||||
|
||||
test('disabling the layer tears the pulses down completely', (t) => {
|
||||
const ring = ringRendering(t);
|
||||
const { state, scene, rendering, motion, pulseFrames } = ring;
|
||||
|
||||
Reference in new issue
Block a user