Unverified Commit 371667ec authored by David Reveman's avatar David Reveman Committed by GitHub

Fix resampling of down, up, and remove events. (#67080)

This adds the neccessary synthetic move/hover events to
resampler, which is needed for position to not change as
part of a down, up, or remove event.
Co-authored-by: 's avatarDavid Reveman <reveman@google.com>
parent e45157fc
...@@ -106,6 +106,18 @@ class PointerEventResampler { ...@@ -106,6 +106,18 @@ class PointerEventResampler {
); );
} }
PointerEvent _toMoveOrHoverEvent(
PointerEvent event,
Offset position,
Offset delta,
int pointerIdentifier,
Duration timeStamp,
bool isDown,
) {
return isDown ? _toMoveEvent(event, position, delta, pointerIdentifier, timeStamp)
: _toHoverEvent(event, position, delta, timeStamp);
}
Offset _positionAt(Duration sampleTime) { Offset _positionAt(Duration sampleTime) {
// Use `next` position by default. // Use `next` position by default.
double x = _next?.position.dx ?? 0.0; double x = _next?.position.dx ?? 0.0;
...@@ -224,13 +236,21 @@ class PointerEventResampler { ...@@ -224,13 +236,21 @@ class PointerEventResampler {
// Skip `move` and `hover` events as they are automatically // Skip `move` and `hover` events as they are automatically
// generated when the position has changed. // generated when the position has changed.
if (event is! PointerMoveEvent && event is! PointerHoverEvent) { if (event is! PointerMoveEvent && event is! PointerHoverEvent) {
// Add synthetics `move` or `hover` event if position has changed.
// Note: Devices without `hover` events are expected to always have
// `add` and `down` events with the same position and this logic will
// therefor never produce `hover` events.
if (position != _position) {
final Offset delta = position - _position;
callback(_toMoveOrHoverEvent(event, position, delta, _pointerIdentifier, sampleTime, wasDown));
_position = position;
}
callback(event.copyWith( callback(event.copyWith(
position: position, position: position,
delta: position - _position, delta: Offset.zero,
pointer: pointerIdentifier, pointer: pointerIdentifier,
timeStamp: sampleTime, timeStamp: sampleTime,
)); ));
_position = position;
} }
_queuedEvents.removeFirst(); _queuedEvents.removeFirst();
...@@ -248,10 +268,7 @@ class PointerEventResampler { ...@@ -248,10 +268,7 @@ class PointerEventResampler {
final PointerEvent? next = _next; final PointerEvent? next = _next;
if (position != _position && next != null) { if (position != _position && next != null) {
final Offset delta = position - _position; final Offset delta = position - _position;
final PointerEvent event = _isDown callback(_toMoveOrHoverEvent(next, position, delta, _pointerIdentifier, sampleTime, _isDown));
? _toMoveEvent(next, position, delta, _pointerIdentifier, sampleTime)
: _toHoverEvent(next, position, delta, sampleTime);
callback(event);
_position = position; _position = position;
} }
} }
......
...@@ -97,9 +97,13 @@ void main() { ...@@ -97,9 +97,13 @@ void main() {
// Now the system time is epoch + 11ms // Now the system time is epoch + 11ms
await tester.pump(const Duration(milliseconds: 2)); await tester.pump(const Duration(milliseconds: 2));
expect(events.length, 3); expect(events.length, 4);
expect(events[2].timeStamp, currentTestFrameTime() + kSamplingOffset); expect(events[2].timeStamp, currentTestFrameTime() + kSamplingOffset);
expect(events[2].runtimeType, equals(PointerUpEvent)); expect(events[2].runtimeType, equals(PointerMoveEvent));
expect(events[2].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0)); expect(events[2].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
expect(events[2].delta, Offset(15.0 / ui.window.devicePixelRatio, 0.0));
expect(events[3].timeStamp, currentTestFrameTime() + kSamplingOffset);
expect(events[3].runtimeType, equals(PointerUpEvent));
expect(events[3].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
}); });
} }
...@@ -104,46 +104,64 @@ void main() { ...@@ -104,46 +104,64 @@ void main() {
resampler.sample(const Duration(microseconds: 2500), result.add); resampler.sample(const Duration(microseconds: 2500), result.add);
// Down pointer event should have been returned. // Hover and down pointer events should have been returned.
expect(result.length, 2); expect(result.length, 3);
expect(result[1].timeStamp, const Duration(microseconds: 2500)); expect(result[1].timeStamp, const Duration(microseconds: 2500));
expect(result[1] is PointerDownEvent, true); expect(result[1] is PointerHoverEvent, true);
expect(result[1].position.dx, 15.0); expect(result[1].position.dx, 15.0);
expect(result[1].position.dy, 35.0); expect(result[1].position.dy, 35.0);
expect(result[1].delta.dx, 10.0);
expect(result[1].delta.dy, -10.0);
expect(result[2].timeStamp, const Duration(microseconds: 2500));
expect(result[2] is PointerDownEvent, true);
expect(result[2].position.dx, 15.0);
expect(result[2].position.dy, 35.0);
resampler.sample(const Duration(microseconds: 3500), result.add); resampler.sample(const Duration(microseconds: 3500), result.add);
// Move pointer event should have been returned. // Move pointer event should have been returned.
expect(result.length, 3); expect(result.length, 4);
expect(result[2].timeStamp, const Duration(microseconds: 3500)); expect(result[3].timeStamp, const Duration(microseconds: 3500));
expect(result[2] is PointerMoveEvent, true); expect(result[3] is PointerMoveEvent, true);
expect(result[2].position.dx, 25.0); expect(result[3].position.dx, 25.0);
expect(result[2].position.dy, 25.0); expect(result[3].position.dy, 25.0);
expect(result[2].delta.dx, 10.0); expect(result[3].delta.dx, 10.0);
expect(result[2].delta.dy, -10.0); expect(result[3].delta.dy, -10.0);
resampler.sample(const Duration(microseconds: 4500), result.add); resampler.sample(const Duration(microseconds: 4500), result.add);
// Up pointer event should have been returned. // Move and up pointer events should have been returned.
expect(result.length, 4); expect(result.length, 6);
expect(result[3].timeStamp, const Duration(microseconds: 4500)); expect(result[4].timeStamp, const Duration(microseconds: 4500));
expect(result[3] is PointerUpEvent, true); expect(result[4] is PointerMoveEvent, true);
expect(result[3].position.dx, 35.0); expect(result[4].position.dx, 35.0);
expect(result[3].position.dy, 15.0); expect(result[4].position.dy, 15.0);
expect(result[4].delta.dx, 10.0);
expect(result[4].delta.dy, -10.0);
expect(result[5].timeStamp, const Duration(microseconds: 4500));
expect(result[5] is PointerUpEvent, true);
expect(result[5].position.dx, 35.0);
expect(result[5].position.dy, 15.0);
resampler.sample(const Duration(microseconds: 5500), result.add); resampler.sample(const Duration(microseconds: 5500), result.add);
// Remove pointer event should have been returned. // Hover and remove pointer events should have been returned.
expect(result.length, 5); expect(result.length, 8);
expect(result[4].timeStamp, const Duration(microseconds: 5500)); expect(result[6].timeStamp, const Duration(microseconds: 5500));
expect(result[4] is PointerRemovedEvent, true); expect(result[6] is PointerHoverEvent, true);
expect(result[4].position.dx, 45.0); expect(result[6].position.dx, 45.0);
expect(result[4].position.dy, 5.0); expect(result[6].position.dy, 5.0);
expect(result[6].delta.dx, 10.0);
expect(result[6].delta.dy, -10.0);
expect(result[7].timeStamp, const Duration(microseconds: 5500));
expect(result[7] is PointerRemovedEvent, true);
expect(result[7].position.dx, 45.0);
expect(result[7].position.dy, 5.0);
resampler.sample(const Duration(microseconds: 6500), result.add); resampler.sample(const Duration(microseconds: 6500), result.add);
// No pointer event should have been returned. // No pointer event should have been returned.
expect(result.length, 5); expect(result.length, 8);
}); });
test('stream', () { test('stream', () {
...@@ -201,19 +219,25 @@ void main() { ...@@ -201,19 +219,25 @@ void main() {
resampler.sample(const Duration(microseconds: 2500), result.add); resampler.sample(const Duration(microseconds: 2500), result.add);
// Down pointer event should have been returned. // Hover and down pointer events should have been returned.
expect(result.length, 2); expect(result.length, 3);
expect(result[1].timeStamp, const Duration(microseconds: 2500)); expect(result[1].timeStamp, const Duration(microseconds: 2500));
expect(result[1] is PointerDownEvent, true); expect(result[1] is PointerHoverEvent, true);
expect(result[1].position.dx, 15.0); expect(result[1].position.dx, 15.0);
expect(result[1].position.dy, 35.0); expect(result[1].position.dy, 35.0);
expect(result[1].delta.dx, 10.0);
expect(result[1].delta.dy, -10.0);
expect(result[2].timeStamp, const Duration(microseconds: 2500));
expect(result[2] is PointerDownEvent, true);
expect(result[2].position.dx, 15.0);
expect(result[2].position.dy, 35.0);
resampler.addEvent(event3); resampler.addEvent(event3);
resampler.sample(const Duration(microseconds: 2500), result.add); resampler.sample(const Duration(microseconds: 2500), result.add);
// No more pointer events should have been returned. // No more pointer events should have been returned.
expect(result.length, 2); expect(result.length, 3);
// //
// Advance sample time to 3.5 ms. // Advance sample time to 3.5 ms.
...@@ -222,20 +246,20 @@ void main() { ...@@ -222,20 +246,20 @@ void main() {
resampler.sample(const Duration(microseconds: 3500), result.add); resampler.sample(const Duration(microseconds: 3500), result.add);
// Move pointer event should have been returned. // Move pointer event should have been returned.
expect(result.length, 3); expect(result.length, 4);
expect(result[2].timeStamp, const Duration(microseconds: 3500)); expect(result[3].timeStamp, const Duration(microseconds: 3500));
expect(result[2] is PointerMoveEvent, true); expect(result[3] is PointerMoveEvent, true);
expect(result[2].position.dx, 25.0); expect(result[3].position.dx, 25.0);
expect(result[2].position.dy, 25.0); expect(result[3].position.dy, 25.0);
expect(result[2].delta.dx, 10.0); expect(result[3].delta.dx, 10.0);
expect(result[2].delta.dy, -10.0); expect(result[3].delta.dy, -10.0);
resampler.addEvent(event4); resampler.addEvent(event4);
resampler.sample(const Duration(microseconds: 3500), result.add); resampler.sample(const Duration(microseconds: 3500), result.add);
// No more pointer events should have been returned. // No more pointer events should have been returned.
expect(result.length, 3); expect(result.length, 4);
// //
// Advance sample time to 4.5 ms. // Advance sample time to 4.5 ms.
...@@ -243,19 +267,25 @@ void main() { ...@@ -243,19 +267,25 @@ void main() {
resampler.sample(const Duration(microseconds: 4500), result.add); resampler.sample(const Duration(microseconds: 4500), result.add);
// Up pointer event should have been returned. // Move and up pointer events should have been returned.
expect(result.length, 4); expect(result.length, 6);
expect(result[3].timeStamp, const Duration(microseconds: 4500)); expect(result[4].timeStamp, const Duration(microseconds: 4500));
expect(result[3] is PointerUpEvent, true); expect(result[4] is PointerMoveEvent, true);
expect(result[3].position.dx, 35.0); expect(result[4].position.dx, 35.0);
expect(result[3].position.dy, 15.0); expect(result[4].position.dy, 15.0);
expect(result[4].delta.dx, 10.0);
expect(result[4].delta.dy, -10.0);
expect(result[5].timeStamp, const Duration(microseconds: 4500));
expect(result[5] is PointerUpEvent, true);
expect(result[5].position.dx, 35.0);
expect(result[5].position.dy, 15.0);
resampler.addEvent(event5); resampler.addEvent(event5);
resampler.sample(const Duration(microseconds: 4500), result.add); resampler.sample(const Duration(microseconds: 4500), result.add);
// No more pointer events should have been returned. // No more pointer events should have been returned.
expect(result.length, 4); expect(result.length, 6);
// //
// Advance sample time to 5.5 ms. // Advance sample time to 5.5 ms.
...@@ -263,12 +293,18 @@ void main() { ...@@ -263,12 +293,18 @@ void main() {
resampler.sample(const Duration(microseconds: 5500), result.add); resampler.sample(const Duration(microseconds: 5500), result.add);
// Remove pointer event should have been returned. // Hover and remove pointer event should have been returned.
expect(result.length, 5); expect(result.length, 8);
expect(result[4].timeStamp, const Duration(microseconds: 5500)); expect(result[6].timeStamp, const Duration(microseconds: 5500));
expect(result[4] is PointerRemovedEvent, true); expect(result[6] is PointerHoverEvent, true);
expect(result[4].position.dx, 45.0); expect(result[6].position.dx, 45.0);
expect(result[4].position.dy, 5.0); expect(result[6].position.dy, 5.0);
expect(result[6].delta.dx, 10.0);
expect(result[6].delta.dy, -10.0);
expect(result[7].timeStamp, const Duration(microseconds: 5500));
expect(result[7] is PointerRemovedEvent, true);
expect(result[7].position.dx, 45.0);
expect(result[7].position.dy, 5.0);
// //
// Advance sample time to 6.5 ms. // Advance sample time to 6.5 ms.
...@@ -277,7 +313,7 @@ void main() { ...@@ -277,7 +313,7 @@ void main() {
resampler.sample(const Duration(microseconds: 6500), result.add); resampler.sample(const Duration(microseconds: 6500), result.add);
// No pointer events should have been returned. // No pointer events should have been returned.
expect(result.length, 5); expect(result.length, 8);
}); });
test('quick tap', () { test('quick tap', () {
...@@ -376,16 +412,22 @@ void main() { ...@@ -376,16 +412,22 @@ void main() {
resampler.sample(const Duration(microseconds: 2500), result.add); resampler.sample(const Duration(microseconds: 2500), result.add);
// Last two pointer events should have been returned. // Move, up and removed pointer events should have been returned.
expect(result.length, 6); expect(result.length, 7);
expect(result[4].timeStamp, const Duration(microseconds: 2500)); expect(result[4].timeStamp, const Duration(microseconds: 2500));
expect(result[4] is PointerUpEvent, true); expect(result[4] is PointerMoveEvent, true);
expect(result[4].position.dx, 15.0); expect(result[4].position.dx, 15.0);
expect(result[4].position.dy, 0.0); expect(result[4].position.dy, 0.0);
expect(result[4].delta.dx, 5.0);
expect(result[4].delta.dy, 0.0);
expect(result[5].timeStamp, const Duration(microseconds: 2500)); expect(result[5].timeStamp, const Duration(microseconds: 2500));
expect(result[5] is PointerRemovedEvent, true); expect(result[5] is PointerUpEvent, true);
expect(result[5].position.dx, 15.0); expect(result[5].position.dx, 15.0);
expect(result[5].position.dy, 0.0); expect(result[5].position.dy, 0.0);
expect(result[6].timeStamp, const Duration(microseconds: 2500));
expect(result[6] is PointerRemovedEvent, true);
expect(result[6].position.dx, 15.0);
expect(result[6].position.dy, 0.0);
}); });
test('advance fast', () { test('advance fast', () {
...@@ -422,21 +464,27 @@ void main() { ...@@ -422,21 +464,27 @@ void main() {
resampler.sample(const Duration(microseconds: 5500), result.add); resampler.sample(const Duration(microseconds: 5500), result.add);
// Up and removed pointer events should have been returned. // Move, up and removed pointer events should have been returned.
expect(result.length, 4); expect(result.length, 5);
expect(result[2].timeStamp, const Duration(microseconds: 5500)); expect(result[2].timeStamp, const Duration(microseconds: 5500));
expect(result[2] is PointerUpEvent, true); expect(result[2] is PointerMoveEvent, true);
expect(result[2].position.dx, 30.0); expect(result[2].position.dx, 30.0);
expect(result[2].position.dy, 0.0); expect(result[2].position.dy, 0.0);
expect(result[2].delta.dx, 17.5);
expect(result[2].delta.dy, 0.0);
expect(result[3].timeStamp, const Duration(microseconds: 5500)); expect(result[3].timeStamp, const Duration(microseconds: 5500));
expect(result[3] is PointerRemovedEvent, true); expect(result[3] is PointerUpEvent, true);
expect(result[3].position.dx, 30.0); expect(result[3].position.dx, 30.0);
expect(result[3].position.dy, 0.0); expect(result[3].position.dy, 0.0);
expect(result[4].timeStamp, const Duration(microseconds: 5500));
expect(result[4] is PointerRemovedEvent, true);
expect(result[4].position.dx, 30.0);
expect(result[4].position.dy, 0.0);
resampler.sample(const Duration(microseconds: 6500), result.add); resampler.sample(const Duration(microseconds: 6500), result.add);
// No pointer events should have been returned. // No pointer events should have been returned.
expect(result.length, 4); expect(result.length, 5);
}); });
test('skip', () { test('skip', () {
...@@ -476,28 +524,34 @@ void main() { ...@@ -476,28 +524,34 @@ void main() {
resampler.sample(const Duration(microseconds: 4500), result.add); resampler.sample(const Duration(microseconds: 4500), result.add);
// All remaining pointer events should have been returned. // All remaining pointer events should have been returned.
expect(result.length, 6); expect(result.length, 7);
expect(result[2].timeStamp, const Duration(microseconds: 4500)); expect(result[2].timeStamp, const Duration(microseconds: 4500));
expect(result[2] is PointerUpEvent, true); expect(result[2] is PointerMoveEvent, true);
expect(result[2].position.dx, 25.0); expect(result[2].position.dx, 25.0);
expect(result[2].position.dy, 0.0); expect(result[2].position.dy, 0.0);
expect(result[2].delta.dx, 20.0);
expect(result[2].delta.dy, 0.0);
expect(result[3].timeStamp, const Duration(microseconds: 4500)); expect(result[3].timeStamp, const Duration(microseconds: 4500));
expect(result[3] is PointerDownEvent, true); expect(result[3] is PointerUpEvent, true);
expect(result[3].position.dx, 25.0); expect(result[3].position.dx, 25.0);
expect(result[3].position.dy, 0.0); expect(result[3].position.dy, 0.0);
expect(result[4].timeStamp, const Duration(microseconds: 4500)); expect(result[4].timeStamp, const Duration(microseconds: 4500));
expect(result[4] is PointerUpEvent, true); expect(result[4] is PointerDownEvent, true);
expect(result[4].position.dx, 25.0); expect(result[4].position.dx, 25.0);
expect(result[4].position.dy, 0.0); expect(result[4].position.dy, 0.0);
expect(result[5].timeStamp, const Duration(microseconds: 4500)); expect(result[5].timeStamp, const Duration(microseconds: 4500));
expect(result[5] is PointerRemovedEvent, true); expect(result[5] is PointerUpEvent, true);
expect(result[5].position.dx, 25.0); expect(result[5].position.dx, 25.0);
expect(result[5].position.dy, 0.0); expect(result[5].position.dy, 0.0);
expect(result[6].timeStamp, const Duration(microseconds: 4500));
expect(result[6] is PointerRemovedEvent, true);
expect(result[6].position.dx, 25.0);
expect(result[6].position.dy, 0.0);
resampler.sample(const Duration(microseconds: 5500), result.add); resampler.sample(const Duration(microseconds: 5500), result.add);
// No pointer events should have been returned. // No pointer events should have been returned.
expect(result.length, 6); expect(result.length, 7);
}); });
test('skip all', () { test('skip all', () {
...@@ -602,4 +656,65 @@ void main() { ...@@ -602,4 +656,65 @@ void main() {
// No pointer events should have been returned. // No pointer events should have been returned.
expect(result.length, 5); expect(result.length, 5);
}); });
test('synthetic move', () {
final PointerEventResampler resampler = PointerEventResampler();
final PointerEvent event0 = _createSimulatedPointerAddedEvent(1000, 0.0, 0.0);
final PointerEvent event1 = _createSimulatedPointerDownEvent(2000, 0.0, 0.0);
final PointerEvent event2 = _createSimulatedPointerMoveEvent(3000, 10.0, 0.0, 10.0, 0.0);
final PointerEvent event3 = _createSimulatedPointerUpEvent(4000, 10.0, 0.0);
final PointerEvent event4 = _createSimulatedPointerRemovedEvent(5000, 10.0, 0.0);
resampler
..addEvent(event0)
..addEvent(event1)
..addEvent(event2)
..addEvent(event3)
..addEvent(event4);
final List<PointerEvent> result = <PointerEvent>[];
resampler.sample(const Duration(microseconds: 500), result.add);
// No pointer events should have been returned.
expect(result.isEmpty, true);
resampler.sample(const Duration(microseconds: 2000), result.add);
// Added and down pointer events should have been returned.
expect(result.length, 2);
expect(result[0].timeStamp, const Duration(microseconds: 2000));
expect(result[0] is PointerAddedEvent, true);
expect(result[0].position.dx, 0.0);
expect(result[0].position.dy, 0.0);
expect(result[1].timeStamp, const Duration(microseconds: 2000));
expect(result[1] is PointerDownEvent, true);
expect(result[1].position.dx, 0.0);
expect(result[1].position.dy, 0.0);
resampler.sample(const Duration(microseconds: 5000), result.add);
// All remaining pointer events and a synthetic move event should
// have been returned.
expect(result.length, 5);
expect(result[2].timeStamp, const Duration(microseconds: 5000));
expect(result[2] is PointerMoveEvent, true);
expect(result[2].position.dx, 10.0);
expect(result[2].position.dy, 0.0);
expect(result[2].delta.dx, 10.0);
expect(result[2].delta.dy, 0.0);
expect(result[3].timeStamp, const Duration(microseconds: 5000));
expect(result[3] is PointerUpEvent, true);
expect(result[3].position.dx, 10.0);
expect(result[3].position.dy, 0.0);
expect(result[4].timeStamp, const Duration(microseconds: 5000));
expect(result[4] is PointerRemovedEvent, true);
expect(result[4].position.dx, 10.0);
expect(result[4].position.dy, 0.0);
resampler.sample(const Duration(microseconds: 10000), result.add);
// No pointer events should have been returned.
expect(result.length, 5);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment