From c998423db6f6f12c6e9c4c168201e81408dc9c07 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Dec 2019 18:18:14 -0800 Subject: [PATCH 01/28] Fix right click --- .../lib/src/engine/pointer_binding.dart | 75 ++++++++++++++----- .../test/engine/pointer_binding_test.dart | 72 ++++++++++++++++++ 2 files changed, 127 insertions(+), 20 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 46e0c2f751142..ade7d940ed158 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -261,19 +261,31 @@ abstract class BaseAdapter { const int _kPrimaryMouseButton = 0x1; const int _kSecondaryMouseButton = 0x2; -int _pointerButtonFromHtmlEvent(html.Event event) { +typedef _GetDeviceButton = int Function(); + +int _pointerButtonFromHtmlEvent(html.Event event, {_GetDeviceButton getDefaultButton}) { if (event is html.PointerEvent) { final html.PointerEvent pointerEvent = event; - return pointerEvent.button == 2 - ? _kSecondaryMouseButton - : _kPrimaryMouseButton; + switch (pointerEvent.button) { + case 1: + return _kPrimaryMouseButton; + case 2: + return _kSecondaryMouseButton; + default: + break; + } } else if (event is html.MouseEvent) { final html.MouseEvent mouseEvent = event; - return mouseEvent.button == 2 - ? _kSecondaryMouseButton - : _kPrimaryMouseButton; + switch (mouseEvent.button) { + case 1: + return _kPrimaryMouseButton; + case 2: + return _kSecondaryMouseButton; + default: + break; + } } - return _kPrimaryMouseButton; + return getDefaultButton != null ? getDefaultButton() : _kPrimaryMouseButton; } int _deviceFromHtmlEvent(event) { @@ -297,13 +309,18 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointerdown', (html.Event event) { final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); + final int pointerButton = _pointerButtonDefaultToLastButton(event, device); if (_isButtonDown(device, pointerButton)) { // TODO(flutter_web): Remove this temporary fix for right click // on web platform once context guesture is implemented. _callback(_convertEventToPointerData(ui.PointerChange.up, event)); } _updateButtonDownState(device, pointerButton, true); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + _callback(_convertEventToPointerData( + ui.PointerChange.down, + event, + buttons: pointerButton, + )); }); _addEventListener('pointermove', (html.Event event) { @@ -314,20 +331,22 @@ class PointerAdapter extends BaseAdapter { final html.PointerEvent pointerEvent = event; final int pointerButton = _pointerButtonFromHtmlEvent(pointerEvent); final int device = _deviceFromHtmlEvent(event); - final List data = _convertEventToPointerData( - _isButtonDown(device, pointerButton) - ? ui.PointerChange.move - : ui.PointerChange.hover, - pointerEvent); - _callback(data); + final int pointerButton = _pointerButtonDefaultToLastButton(event, device); + _callback(_convertEventToPointerData( + _isButtonDown(device, pointerButton) + ? ui.PointerChange.move + : ui.PointerChange.hover, + pointerEvent, + buttons: pointerButton, + )); }); _addEventListener('pointerup', (html.Event event) { // The pointer could have been released by a `pointerout` event, in which // case `pointerup` should have no effect. - final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); - if (!_isButtonDown(device, pointerButton)) { + final int pointerButton = _pointerButtonDefaultToLastButton(event, device); + if (pointerButton == 0) { return; } _updateButtonDownState(device, pointerButton, false); @@ -339,6 +358,7 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointercancel', (html.Event event) { final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); + final int pointerButton = _pointerButtonDefaultToLastButton(event, device); _updateButtonDownState(pointerButton, device, false); _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); @@ -355,10 +375,25 @@ class PointerAdapter extends BaseAdapter { }); } + int _pointerButtonDefaultToLastButton(html.PointerEvent pointerEvent, int device) { + return _pointerButtonFromHtmlEvent( + pointerEvent, + getDefaultButton: () { + int buttons = 0; + for (_PressedButton pressedButton in _pressedButtons) { + if (pressedButton.deviceId == device) + buttons |= pressedButton.button; + } + return buttons; + }, + ); + } + List _convertEventToPointerData( ui.PointerChange change, - html.PointerEvent evt, - ) { + html.PointerEvent evt, { + int buttons, + }) { final List allEvents = _expandEvents(evt); final List data = []; for (int i = 0; i < allEvents.length; i++) { @@ -371,7 +406,7 @@ class PointerAdapter extends BaseAdapter { device: event.pointerId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, + buttons: buttons ?? event.buttons, pressure: event.pressure, pressureMin: 0.0, pressureMax: 1.0, diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index cee6b8f16de14..cce8ff6cfa559 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -371,6 +371,78 @@ void main() { expect(packets[3].data[1].physicalDeltaX, equals(0.0)); expect(packets[3].data[1].physicalDeltaY, equals(0.0)); }); + + test('synthesizes a pointerup event when pointermove comes before the up', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'button': 2, + 'clientX': 10, + 'clientY': 11, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 20.0, + 'clientY': 21.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 20.0, + 'clientY': 21.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'clientX': 20.0, + 'clientY': 21.0, + })); + + expect(packets, hasLength(4)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10)); + expect(packets[0].data[1].physicalY, equals(11)); + expect(packets[0].data[1].buttons, equals(2)); + + expect(packets[1].data, hasLength(1)); + expect(packets[1].data[0].change, equals(ui.PointerChange.move)); + expect(packets[1].data[0].synthesized, equals(false)); + expect(packets[1].data[0].physicalX, equals(20)); + expect(packets[1].data[0].physicalY, equals(21)); + expect(packets[1].data[0].buttons, equals(2)); + + expect(packets[2].data, hasLength(1)); + expect(packets[2].data[0].change, equals(ui.PointerChange.move)); + expect(packets[2].data[0].synthesized, equals(false)); + expect(packets[2].data[0].physicalX, equals(20)); + expect(packets[2].data[0].physicalY, equals(21)); + expect(packets[2].data[0].buttons, equals(2)); + + expect(packets[3].data, hasLength(1)); + expect(packets[3].data[0].change, equals(ui.PointerChange.up)); + expect(packets[3].data[0].synthesized, equals(false)); + expect(packets[3].data[0].physicalX, equals(20)); + expect(packets[3].data[0].physicalY, equals(21)); + expect(packets[3].data[0].buttons, equals(0)); + }); }); } From 9afaffe588eda12603678dc59ac486d1bd8618f2 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Dec 2019 12:28:00 -0800 Subject: [PATCH 02/28] Add button converter, refactor _pressedButtons --- .../lib/src/engine/pointer_binding.dart | 91 +++---- .../test/engine/pointer_binding_test.dart | 253 ++++++++++++++++-- 2 files changed, 270 insertions(+), 74 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index ade7d940ed158..6a2cdc0a96fcb 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -10,6 +10,13 @@ const bool _debugLogPointerEvents = false; /// The signature of a callback that handles pointer events. typedef PointerDataCallback = void Function(List); +// The mask for the bitfield of event buttons. Buttons not contained in this +// mask are cut off. +// +// In Flutter we used `kMaxUnsignedSMI`, but since that value is not available +// here, we use an already very large number (30 bits). +const int _kButtonsMask = 0x3FFFFFFF; + class PointerBinding { /// The singleton instance of this object. static PointerBinding get instance => _instance; @@ -97,24 +104,6 @@ class PointerSupportDetector { 'pointers:$hasPointerEvents, touch:$hasTouchEvents, mouse:$hasMouseEvents'; } -class _PressedButton { - const _PressedButton(this.deviceId, this.button); - - // The id of the device pressing the button. - final int deviceId; - - // The id of the button being pressed. - final int button; - - bool operator ==(other) { - if (other is! _PressedButton) return false; - final _PressedButton otherButton = other; - return deviceId == otherButton.deviceId && button == otherButton.button; - } - - int get hashCode => ((13801 + deviceId) * 37) + button; -} - /// Common functionality that's shared among adapters. abstract class BaseAdapter { BaseAdapter(this._callback, this.domRenderer, this._pointerDataConverter) { @@ -131,19 +120,26 @@ abstract class BaseAdapter { PointerDataCallback _callback; PointerDataConverter _pointerDataConverter; - // A set of the buttons that are currently being pressed. - Set<_PressedButton> _pressedButtons = Set<_PressedButton>(); + // A map from devices to buttons that each device has pressed. + Map _pressedButtons = {}; + + int _deviceDownButtons(int device) { + return _pressedButtons[device] ?? 0; + } bool _isButtonDown(int device, int button) { - return _pressedButtons.contains(_PressedButton(device, button)); + return _deviceDownButtons(device) & button != 0; } void _updateButtonDownState(int device, int button, bool value) { + final int oldButtons = _deviceDownButtons(device); + int newButtons; if (value) { - _pressedButtons.add(_PressedButton(device, button)); + newButtons = oldButtons | button; } else { - _pressedButtons.remove(_PressedButton(device, button)); + newButtons = oldButtons & ~button; } + _pressedButtons[device] = newButtons & _kButtonsMask; } /// Each subclass is expected to override this method to attach its own event @@ -260,6 +256,7 @@ abstract class BaseAdapter { const int _kPrimaryMouseButton = 0x1; const int _kSecondaryMouseButton = 0x2; +const int _kMiddleMouseButton = 0x4; typedef _GetDeviceButton = int Function(); @@ -307,9 +304,9 @@ class PointerAdapter extends BaseAdapter { @override void _setup() { _addEventListener('pointerdown', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); - final int pointerButton = _pointerButtonDefaultToLastButton(event, device); + final html.PointerEvent pointerEvent = event; + final int pointerButton = _downEventButtons(pointerEvent); if (_isButtonDown(device, pointerButton)) { // TODO(flutter_web): Remove this temporary fix for right click // on web platform once context guesture is implemented. @@ -329,9 +326,8 @@ class PointerAdapter extends BaseAdapter { // This check is currently defaulting to primary button for now. // Change this when context gesture is implemented in flutter framework. final html.PointerEvent pointerEvent = event; - final int pointerButton = _pointerButtonFromHtmlEvent(pointerEvent); final int device = _deviceFromHtmlEvent(event); - final int pointerButton = _pointerButtonDefaultToLastButton(event, device); + final int pointerButton = _moveEventButtons(event, device); _callback(_convertEventToPointerData( _isButtonDown(device, pointerButton) ? ui.PointerChange.move @@ -345,21 +341,20 @@ class PointerAdapter extends BaseAdapter { // The pointer could have been released by a `pointerout` event, in which // case `pointerup` should have no effect. final int device = _deviceFromHtmlEvent(event); - final int pointerButton = _pointerButtonDefaultToLastButton(event, device); - if (pointerButton == 0) { + final int currentPointerButton = _deviceDownButtons(device); + if (currentPointerButton == 0) { return; } - _updateButtonDownState(device, pointerButton, false); + _updateButtonDownState(device, currentPointerButton, false); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) _addEventListener('pointercancel', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); - final int pointerButton = _pointerButtonDefaultToLastButton(event, device); - _updateButtonDownState(pointerButton, device, false); + final int currentPointerButton = _deviceDownButtons(device); + _updateButtonDownState(device, currentPointerButton, false); _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); @@ -375,18 +370,24 @@ class PointerAdapter extends BaseAdapter { }); } - int _pointerButtonDefaultToLastButton(html.PointerEvent pointerEvent, int device) { - return _pointerButtonFromHtmlEvent( - pointerEvent, - getDefaultButton: () { - int buttons = 0; - for (_PressedButton pressedButton in _pressedButtons) { - if (pressedButton.deviceId == device) - buttons |= pressedButton.button; - } - return buttons; - }, - ); + int _downEventButtons(html.PointerEvent pointerEvent) { + switch (pointerEvent.button) { + case 0: + return _kPrimaryMouseButton; + case 1: + return _kMiddleMouseButton; + case 2: + return _kSecondaryMouseButton; + default: + return pointerEvent.button; + } + } + + int _moveEventButtons(html.PointerEvent pointerEvent, int device) { + if (pointerEvent.button == -1) { + return _deviceDownButtons(device); + } + return _downEventButtons(pointerEvent); } List _convertEventToPointerData( diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index cce8ff6cfa559..f0cd2131a294b 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -372,44 +372,199 @@ void main() { expect(packets[3].data[1].physicalDeltaY, equals(0.0)); }); - test('synthesizes a pointerup event when pointermove comes before the up', () { + test('correctly convert buttons of down, move and up events', () { List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, + // Add and hover + glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', - 'button': 2, + 'button': -1, 'clientX': 10, 'clientY': 11, })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10)); + expect(packets[0].data[1].physicalY, equals(11)); + expect(packets[0].data[1].buttons, equals(0)); + packets.clear(); + + // Drag with primary button + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerType': 'mouse', + 'button': 0, + 'clientX': 10.0, + 'clientY': 11.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + expect(packets[0].data[0].buttons, equals(1)); + packets.clear(); + glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, 'pointerType': 'mouse', 'button': -1, 'clientX': 20.0, 'clientY': 21.0, })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(1)); + packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, + glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerType': 'mouse', - 'button': -1, + 'button': 0, 'clientX': 20.0, 'clientY': 21.0, })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + + // Drag with secondary button + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerType': 'mouse', + 'button': 2, + 'clientX': 20.0, + 'clientY': 21.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 30.0, + 'clientY': 31.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerType': 'mouse', + 'button': 2, + 'clientX': 30.0, + 'clientY': 31.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + + // Drag with middle button + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerType': 'mouse', + 'button': 1, + 'clientX': 30.0, + 'clientY': 31.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 40.0, + 'clientY': 41.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40)); + expect(packets[0].data[0].physicalY, equals(41)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerType': 'mouse', + 'button': 1, + 'clientX': 40.0, + 'clientY': 41.0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40)); + expect(packets[0].data[0].physicalY, equals(41)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + + test('synthesizes a pointerup event when pointermove comes before the up', () { + // This can happen when the user pops up the context menu by right clicking + // and dismisses it. + // + // - When the user pops up the context menu, there will be an pointerdown + // event with button 2. + // - When the user moves with the context menu, there will be no events. + // - When the user dismisses the context menu by clicking anywhere, there + // will be 1 or a few pointermove event with button -1 at the final + // location, and one pointerup event with the button clicked at the + // final location. + + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, 'pointerType': 'mouse', - 'clientX': 20.0, - 'clientY': 21.0, + 'button': 2, + 'clientX': 10, + 'clientY': 11, })); - expect(packets, hasLength(4)); + expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); expect(packets[0].data[0].synthesized, equals(true)); @@ -421,31 +576,71 @@ void main() { expect(packets[0].data[1].physicalX, equals(10)); expect(packets[0].data[1].physicalY, equals(11)); expect(packets[0].data[1].buttons, equals(2)); + packets.clear(); - expect(packets[1].data, hasLength(1)); - expect(packets[1].data[0].change, equals(ui.PointerChange.move)); - expect(packets[1].data[0].synthesized, equals(false)); - expect(packets[1].data[0].physicalX, equals(20)); - expect(packets[1].data[0].physicalY, equals(21)); - expect(packets[1].data[0].buttons, equals(2)); + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 20.0, + 'clientY': 21.0, + })); - expect(packets[2].data, hasLength(1)); - expect(packets[2].data[0].change, equals(ui.PointerChange.move)); - expect(packets[2].data[0].synthesized, equals(false)); - expect(packets[2].data[0].physicalX, equals(20)); - expect(packets[2].data[0].physicalY, equals(21)); - expect(packets[2].data[0].buttons, equals(2)); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); - expect(packets[3].data, hasLength(1)); - expect(packets[3].data[0].change, equals(ui.PointerChange.up)); - expect(packets[3].data[0].synthesized, equals(false)); - expect(packets[3].data[0].physicalX, equals(20)); - expect(packets[3].data[0].physicalY, equals(21)); - expect(packets[3].data[0].buttons, equals(0)); + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'button': -1, + 'clientX': 20.0, + 'clientY': 21.0, + })); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerId': 1, + 'pointerType': 'mouse', + 'clientX': 20.0, + 'clientY': 21.0, + })); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); }); }); } +void printPackets(List packets) { + print('packets ${packets.length}'); + for (ui.PointerDataPacket packet in packets) { + print('packet'); + for (ui.PointerData data in packet.data) { + print('data ${data.change} ${data.device} ${data.buttons} (${data.physicalX}, ${data.physicalY}) ${data.synthesized}'); + } + } +} + class TestPointerDetector extends PointerSupportDetector { @override final bool hasPointerEvents = true; From 5ce49897d0b684a2b42a6699aba458614a355eed Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Dec 2019 15:59:42 -0800 Subject: [PATCH 03/28] Add correct handling of multiple buttons --- .../lib/src/engine/pointer_binding.dart | 98 ++++++++++--------- .../test/engine/pointer_binding_test.dart | 69 +++++++++++-- 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 6a2cdc0a96fcb..d767c518085df 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -131,13 +131,27 @@ abstract class BaseAdapter { return _deviceDownButtons(device) & button != 0; } - void _updateButtonDownState(int device, int button, bool value) { + // Update the down state of button bitfield `button` of device `device`. + // + // You can choose to set the new value by specifying `pressed`, or by + // toggling the value by `togglePressed: true`, but you must choose one and + // only one of them. + void _updateButtonDownState(int device, int button, { + bool pressed, + bool togglePressed = false, + }) { final int oldButtons = _deviceDownButtons(device); int newButtons; - if (value) { - newButtons = oldButtons | button; + if (!togglePressed) { + assert(pressed != null); + if (pressed) { + newButtons = oldButtons | button; + } else { + newButtons = oldButtons & ~button; + } } else { - newButtons = oldButtons & ~button; + assert(togglePressed); + newButtons = oldButtons ^ button; } _pressedButtons[device] = newButtons & _kButtonsMask; } @@ -306,46 +320,42 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointerdown', (html.Event event) { final int device = _deviceFromHtmlEvent(event); final html.PointerEvent pointerEvent = event; - final int pointerButton = _downEventButtons(pointerEvent); - if (_isButtonDown(device, pointerButton)) { - // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. + final int changedButton = _pointerButtonToFlutterButton(pointerEvent.button); + // TODO(flutter_web): Remove this temporary fix for right click + // on web platform once context guesture is implemented. + if (_isButtonDown(device, changedButton)) { _callback(_convertEventToPointerData(ui.PointerChange.up, event)); } - _updateButtonDownState(device, pointerButton, true); - _callback(_convertEventToPointerData( - ui.PointerChange.down, - event, - buttons: pointerButton, - )); + _updateButtonDownState(device, changedButton, pressed: true); + _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); _addEventListener('pointermove', (html.Event event) { - // TODO(flutter_web): During a drag operation pointermove will set - // button to -1 as opposed to mouse move which sets it to 2. - // This check is currently defaulting to primary button for now. - // Change this when context gesture is implemented in flutter framework. + // During a drag operation pointermove will set button to the changed + // button if there is a button change, or -1 otherwise (dragging). final html.PointerEvent pointerEvent = event; final int device = _deviceFromHtmlEvent(event); - final int pointerButton = _moveEventButtons(event, device); + final int changedButton = _pointerButtonToFlutterButton(pointerEvent.button); + if (changedButton != -1) { + _updateButtonDownState(device, changedButton, togglePressed: true); + } _callback(_convertEventToPointerData( - _isButtonDown(device, pointerButton) - ? ui.PointerChange.move - : ui.PointerChange.hover, + _deviceDownButtons(device) == 0 + ? ui.PointerChange.hover + : ui.PointerChange.move, pointerEvent, - buttons: pointerButton, )); }); _addEventListener('pointerup', (html.Event event) { - // The pointer could have been released by a `pointerout` event, in which - // case `pointerup` should have no effect. final int device = _deviceFromHtmlEvent(event); final int currentPointerButton = _deviceDownButtons(device); + // The pointer could have been released by a `pointerout` event, in which + // case `pointerup` should have no effect. if (currentPointerButton == 0) { return; } - _updateButtonDownState(device, currentPointerButton, false); + _updateButtonDownState(device, currentPointerButton, pressed: false); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); @@ -354,7 +364,7 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointercancel', (html.Event event) { final int device = _deviceFromHtmlEvent(event); final int currentPointerButton = _deviceDownButtons(device); - _updateButtonDownState(device, currentPointerButton, false); + _updateButtonDownState(device, currentPointerButton, pressed: false); _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); @@ -370,8 +380,10 @@ class PointerAdapter extends BaseAdapter { }); } - int _downEventButtons(html.PointerEvent pointerEvent) { - switch (pointerEvent.button) { + int _pointerButtonToFlutterButton(int button) { + switch (button) { + case -1: + return -1; case 0: return _kPrimaryMouseButton; case 1: @@ -379,26 +391,16 @@ class PointerAdapter extends BaseAdapter { case 2: return _kSecondaryMouseButton; default: - return pointerEvent.button; + return button; } } - int _moveEventButtons(html.PointerEvent pointerEvent, int device) { - if (pointerEvent.button == -1) { - return _deviceDownButtons(device); - } - return _downEventButtons(pointerEvent); - } - List _convertEventToPointerData( ui.PointerChange change, - html.PointerEvent evt, { - int buttons, - }) { - final List allEvents = _expandEvents(evt); + html.PointerEvent evt, + ) { final List data = []; - for (int i = 0; i < allEvents.length; i++) { - final html.PointerEvent event = allEvents[i]; + for (html.PointerEvent event in _expandEvents(evt)) { _pointerDataConverter.convert( data, change: change, @@ -407,7 +409,7 @@ class PointerAdapter extends BaseAdapter { device: event.pointerId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: buttons ?? event.buttons, + buttons: _deviceDownButtons(event.pointerId), pressure: event.pressure, pressureMin: 0.0, pressureMax: 1.0, @@ -464,7 +466,7 @@ class TouchAdapter extends BaseAdapter { void _setup() { _addEventListener('touchstart', (html.Event event) { _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, true); + _deviceFromHtmlEvent(event), _kPrimaryMouseButton, pressed: true); _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); @@ -481,7 +483,7 @@ class TouchAdapter extends BaseAdapter { // added. event.preventDefault(); _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, false); + _deviceFromHtmlEvent(event), _kPrimaryMouseButton, pressed: false); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); @@ -539,7 +541,7 @@ class MouseAdapter extends BaseAdapter { // on web platform once context guesture is implemented. _callback(_convertEventToPointerData(ui.PointerChange.up, event)); } - _updateButtonDownState(device, pointerButton, true); + _updateButtonDownState(device, pointerButton, pressed: true); _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); @@ -556,7 +558,7 @@ class MouseAdapter extends BaseAdapter { _addEventListener('mouseup', (html.Event event) { final int device = _deviceFromHtmlEvent(event); - _updateButtonDownState(device, _pointerButtonFromHtmlEvent(event), false); + _updateButtonDownState(device, _pointerButtonFromHtmlEvent(event), pressed: false); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index f0cd2131a294b..deb3e3a05ef9c 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -106,7 +106,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, - 'button': 1, + 'button': -1, })); expect(packets, hasLength(1)); @@ -143,14 +143,14 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, - 'button': 1, + 'button': -1, 'clientX': 10.0, 'clientY': 10.0, })); glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, - 'button': 1, + 'button': -1, 'clientX': 20.0, 'clientY': 20.0, })); @@ -164,7 +164,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, - 'button': 1, + 'button': -1, 'clientX': 40.0, 'clientY': 30.0, })); @@ -178,7 +178,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, - 'button': 1, + 'button': -1, 'clientX': 20.0, 'clientY': 10.0, })); @@ -372,7 +372,7 @@ void main() { expect(packets[3].data[1].physicalDeltaY, equals(0.0)); }); - test('correctly convert buttons of down, move and up events', () { + test('correctly converts buttons of down, move and up events', () { List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); @@ -539,6 +539,63 @@ void main() { packets.clear(); }); + test('correctly handles button changes during a down sequence', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Press LMB. + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerType': 'mouse', + 'button': 0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].buttons, equals(1)); + packets.clear(); + + // Press MMB. + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': 1, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(5)); + packets.clear(); + + // Release LMB. + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': 0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); + + // Release MMB. + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerType': 'mouse', + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + test('synthesizes a pointerup event when pointermove comes before the up', () { // This can happen when the user pops up the context menu by right clicking // and dismisses it. From 6d2e42e5273957228b1354cb16f6f4cde8acae63 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Dec 2019 14:29:48 -0800 Subject: [PATCH 04/28] Use buttons --- .../lib/src/engine/pointer_binding.dart | 47 ++++++++--------- .../test/engine/pointer_binding_test.dart | 50 +++++++++++++++---- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index d767c518085df..e52b6b1740417 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -156,6 +156,10 @@ abstract class BaseAdapter { _pressedButtons[device] = newButtons & _kButtonsMask; } + void _setButtonDownState(int device, int buttons) { + _pressedButtons[device] = buttons & _kButtonsMask; + } + /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. void _setup(); @@ -270,7 +274,6 @@ abstract class BaseAdapter { const int _kPrimaryMouseButton = 0x1; const int _kSecondaryMouseButton = 0x2; -const int _kMiddleMouseButton = 0x4; typedef _GetDeviceButton = int Function(); @@ -320,13 +323,15 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointerdown', (html.Event event) { final int device = _deviceFromHtmlEvent(event); final html.PointerEvent pointerEvent = event; - final int changedButton = _pointerButtonToFlutterButton(pointerEvent.button); // TODO(flutter_web): Remove this temporary fix for right click // on web platform once context guesture is implemented. - if (_isButtonDown(device, changedButton)) { + final int currentButtons = _deviceDownButtons(device); + if (currentButtons != 0) { + _setButtonDownState(device, 0); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); } - _updateButtonDownState(device, changedButton, pressed: true); + final int newButtons = _htmlButtonsToFlutterButtons(pointerEvent.buttons); + _setButtonDownState(device, newButtons); _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); @@ -335,10 +340,8 @@ class PointerAdapter extends BaseAdapter { // button if there is a button change, or -1 otherwise (dragging). final html.PointerEvent pointerEvent = event; final int device = _deviceFromHtmlEvent(event); - final int changedButton = _pointerButtonToFlutterButton(pointerEvent.button); - if (changedButton != -1) { - _updateButtonDownState(device, changedButton, togglePressed: true); - } + final int newButtons = _htmlButtonsToFlutterButtons(pointerEvent.buttons); + _setButtonDownState(device, newButtons); _callback(_convertEventToPointerData( _deviceDownButtons(device) == 0 ? ui.PointerChange.hover @@ -349,13 +352,13 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointerup', (html.Event event) { final int device = _deviceFromHtmlEvent(event); - final int currentPointerButton = _deviceDownButtons(device); + final int currentButtons = _deviceDownButtons(device); + _setButtonDownState(device, 0); // The pointer could have been released by a `pointerout` event, in which // case `pointerup` should have no effect. - if (currentPointerButton == 0) { + if (currentButtons == 0) { return; } - _updateButtonDownState(device, currentPointerButton, pressed: false); _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); @@ -363,8 +366,7 @@ class PointerAdapter extends BaseAdapter { // be able to generate events (example: device is deactivated) _addEventListener('pointercancel', (html.Event event) { final int device = _deviceFromHtmlEvent(event); - final int currentPointerButton = _deviceDownButtons(device); - _updateButtonDownState(device, currentPointerButton, pressed: false); + _setButtonDownState(device, 0); _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); @@ -380,19 +382,12 @@ class PointerAdapter extends BaseAdapter { }); } - int _pointerButtonToFlutterButton(int button) { - switch (button) { - case -1: - return -1; - case 0: - return _kPrimaryMouseButton; - case 1: - return _kMiddleMouseButton; - case 2: - return _kSecondaryMouseButton; - default: - return button; - } + // Transform html.PointerEvent.buttons to Flutter's PointerEvent buttons. + int _htmlButtonsToFlutterButtons(int buttons) { + // Flutter's button definition conveniently matches that of JavaScript + // from primary button (0x1) to forward button (0x10), which allows us to + // avoid transforming it bit by bit. + return buttons; } List _convertEventToPointerData( diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index deb3e3a05ef9c..11665cdae6d5d 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:html' as html; -import 'dart:typed_data'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; @@ -47,12 +46,14 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, })); glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, })); expect(packets, hasLength(3)); @@ -73,12 +74,14 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, })); glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 2, - 'button': 1, + 'button': 0, + 'buttons': 1, })); expect(packets, hasLength(2)); @@ -107,6 +110,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, 'button': -1, + 'buttons': 0, })); expect(packets, hasLength(1)); @@ -125,7 +129,8 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, })); expect(packets, hasLength(1)); @@ -144,6 +149,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, 'button': -1, + 'buttons': 0, 'clientX': 10.0, 'clientY': 10.0, })); @@ -151,13 +157,15 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, 'button': -1, + 'buttons': 0, 'clientX': 20.0, 'clientY': 20.0, })); glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, 'clientX': 20.0, 'clientY': 20.0, })); @@ -165,13 +173,15 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, 'button': -1, + 'buttons': 1, 'clientX': 40.0, 'clientY': 30.0, })); glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 0, 'clientX': 40.0, 'clientY': 30.0, })); @@ -179,13 +189,15 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerId': 1, 'button': -1, + 'buttons': 0, 'clientX': 20.0, 'clientY': 10.0, })); glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerId': 1, - 'button': 1, + 'button': 0, + 'buttons': 1, 'clientX': 20.0, 'clientY': 10.0, })); @@ -382,6 +394,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': -1, + 'buttons': 0, 'clientX': 10, 'clientY': 11, })); @@ -404,6 +417,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerType': 'mouse', 'button': 0, + 'buttons': 1, 'clientX': 10.0, 'clientY': 11.0, })); @@ -419,6 +433,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': -1, + 'buttons': 1, 'clientX': 20.0, 'clientY': 21.0, })); @@ -434,6 +449,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerType': 'mouse', 'button': 0, + 'buttons': 0, 'clientX': 20.0, 'clientY': 21.0, })); @@ -450,6 +466,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerType': 'mouse', 'button': 2, + 'buttons': 2, 'clientX': 20.0, 'clientY': 21.0, })); @@ -465,6 +482,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': -1, + 'buttons': 2, 'clientX': 30.0, 'clientY': 31.0, })); @@ -480,6 +498,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerType': 'mouse', 'button': 2, + 'buttons': 0, 'clientX': 30.0, 'clientY': 31.0, })); @@ -496,6 +515,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerType': 'mouse', 'button': 1, + 'buttons': 4, 'clientX': 30.0, 'clientY': 31.0, })); @@ -511,6 +531,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': -1, + 'buttons': 4, 'clientX': 40.0, 'clientY': 41.0, })); @@ -526,6 +547,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerType': 'mouse', 'button': 1, + 'buttons': 0, 'clientX': 40.0, 'clientY': 41.0, })); @@ -549,6 +571,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerdown', { 'pointerType': 'mouse', 'button': 0, + 'buttons': 1, })); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -564,6 +587,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': 1, + 'buttons': 5, })); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); @@ -576,6 +600,7 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', 'button': 0, + 'buttons': 4, })); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); @@ -587,6 +612,8 @@ void main() { // Release MMB. glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerType': 'mouse', + 'button': 1, + 'buttons': 0, })); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); @@ -617,6 +644,7 @@ void main() { 'pointerId': 1, 'pointerType': 'mouse', 'button': 2, + 'buttons': 2, 'clientX': 10, 'clientY': 11, })); @@ -639,6 +667,7 @@ void main() { 'pointerId': 1, 'pointerType': 'mouse', 'button': -1, + 'buttons': 2, 'clientX': 20.0, 'clientY': 21.0, })); @@ -656,6 +685,7 @@ void main() { 'pointerId': 1, 'pointerType': 'mouse', 'button': -1, + 'buttons': 2, 'clientX': 20.0, 'clientY': 21.0, })); @@ -672,6 +702,8 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointerup', { 'pointerId': 1, 'pointerType': 'mouse', + 'button': 2, + 'buttons': 0, 'clientX': 20.0, 'clientY': 21.0, })); From ac1038fe186de84728dfe12646e5391b2b2080cc Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Dec 2019 14:48:28 -0800 Subject: [PATCH 05/28] Add test for context menu and hold --- .../test/engine/pointer_binding_test.dart | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 11665cdae6d5d..19a563a3ee09f 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -624,16 +624,8 @@ void main() { }); test('synthesizes a pointerup event when pointermove comes before the up', () { - // This can happen when the user pops up the context menu by right clicking - // and dismisses it. - // - // - When the user pops up the context menu, there will be an pointerdown - // event with button 2. - // - When the user moves with the context menu, there will be no events. - // - When the user dismisses the context menu by clicking anywhere, there - // will be 1 or a few pointermove event with button -1 at the final - // location, and one pointerup event with the button clicked at the - // final location. + // This can happen when the user pops up the context menu by right + // clicking, then dismisses it with a left click. List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -717,6 +709,76 @@ void main() { expect(packets[0].data[0].buttons, equals(0)); packets.clear(); }); + + test('correctly handles uncontinuous button changes during a down sequence', () { + // This can happen with the following gesture sequence: + // + // - Pops up the context menu by right clicking, but holds RMB; + // - Clicks LMB; + // - Releases RMB. + + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Press RMB and hold, popping up the context menu. + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerType': 'mouse', + 'button': 2, + 'buttons': 2, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].buttons, equals(2)); + packets.clear(); + + // Press LMB. The event will have "button: -1" here, despite the change + // in "buttons", probably because the "press" gesture was absorbed by + // dismissing the context menu. + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': -1, + 'buttons': 3, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(3)); + packets.clear(); + + // Release LMB. + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerType': 'mouse', + 'button': 0, + 'buttons': 2, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + // Release RMB. + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerType': 'mouse', + 'button': 2, + 'buttons': 0, + })); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); }); } From db4884d5a70b6e1e3905ee084c1506e6e2dcf807 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Dec 2019 17:00:31 -0800 Subject: [PATCH 06/28] Refactor into state manager --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- .../lib/src/engine/pointer_binding.dart | 366 +++++++++--------- 2 files changed, 177 insertions(+), 191 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index a7f6ac1d4cc21..b2fb1c102505e 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -401,7 +401,7 @@ flt-glass-pane * { glassPaneElement .insertBefore(_accesibilityPlaceholder, _sceneHostElement); - PointerBinding(this); + PointerBinding(_glassPaneElement); // Hide the DOM nodes used to render the scene from accessibility, because // the accessibility tree is built from the SemanticsNode tree as a parallel diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index e52b6b1740417..f760c22576c00 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -17,12 +17,17 @@ typedef PointerDataCallback = void Function(List); // here, we use an already very large number (30 bits). const int _kButtonsMask = 0x3FFFFFFF; +// Intentionally set to -1 so it doesn't conflict with other device IDs. +const int _mouseDeviceId = -1; + +const int _kPrimaryMouseButton = 1; + class PointerBinding { /// The singleton instance of this object. static PointerBinding get instance => _instance; static PointerBinding _instance; - PointerBinding(this.domRenderer) { + PointerBinding(this.glassPaneElement) { if (_instance == null) { _instance = this; _pointerDataConverter = PointerDataConverter(); @@ -38,9 +43,9 @@ class PointerBinding { }()); } - final DomRenderer domRenderer; + final html.Element glassPaneElement; PointerSupportDetector _detector; - BaseAdapter _adapter; + _BaseAdapter _adapter; PointerDataConverter _pointerDataConverter; /// Should be used in tests to define custom detection of pointer support. /// @@ -70,15 +75,15 @@ class PointerBinding { } } - BaseAdapter _createAdapter() { + _BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return PointerAdapter(_onPointerData, domRenderer, _pointerDataConverter); + return PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasTouchEvents) { - return TouchAdapter(_onPointerData, domRenderer, _pointerDataConverter); + return TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasMouseEvents) { - return MouseAdapter(_onPointerData, domRenderer, _pointerDataConverter); + return MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } return null; } @@ -105,8 +110,8 @@ class PointerSupportDetector { } /// Common functionality that's shared among adapters. -abstract class BaseAdapter { - BaseAdapter(this._callback, this.domRenderer, this._pointerDataConverter) { +abstract class _BaseAdapter { + _BaseAdapter(this._callback, this.glassPaneElement, this._pointerDataConverter) { _setup(); } @@ -116,65 +121,24 @@ abstract class BaseAdapter { /// Listeners that are registered through native javascript api. static final Map _nativeListeners = {}; - final DomRenderer domRenderer; + final html.Element glassPaneElement; PointerDataCallback _callback; PointerDataConverter _pointerDataConverter; - // A map from devices to buttons that each device has pressed. - Map _pressedButtons = {}; - - int _deviceDownButtons(int device) { - return _pressedButtons[device] ?? 0; - } - - bool _isButtonDown(int device, int button) { - return _deviceDownButtons(device) & button != 0; - } - - // Update the down state of button bitfield `button` of device `device`. - // - // You can choose to set the new value by specifying `pressed`, or by - // toggling the value by `togglePressed: true`, but you must choose one and - // only one of them. - void _updateButtonDownState(int device, int button, { - bool pressed, - bool togglePressed = false, - }) { - final int oldButtons = _deviceDownButtons(device); - int newButtons; - if (!togglePressed) { - assert(pressed != null); - if (pressed) { - newButtons = oldButtons | button; - } else { - newButtons = oldButtons & ~button; - } - } else { - assert(togglePressed); - newButtons = oldButtons ^ button; - } - _pressedButtons[device] = newButtons & _kButtonsMask; - } - - void _setButtonDownState(int device, int buttons) { - _pressedButtons[device] = buttons & _kButtonsMask; - } - /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. void _setup(); /// Remove all active event listeners. void clearListeners() { - final html.Element glassPane = domRenderer.glassPaneElement; _listeners.forEach((String eventName, html.EventListener listener) { - glassPane.removeEventListener(eventName, listener, true); + glassPaneElement.removeEventListener(eventName, listener, true); }); // For native listener, we will need to remove it through native javascript // api. _nativeListeners.forEach((String eventName, html.EventListener listener) { js_util.callMethod( - domRenderer.glassPaneElement, + glassPaneElement, 'removeEventListener', [ 'wheel', listener, @@ -198,19 +162,21 @@ abstract class BaseAdapter { } }; _listeners[eventName] = loggedHandler; - domRenderer.glassPaneElement + glassPaneElement .addEventListener(eventName, loggedHandler, true); } /// Converts a floating number timestamp (in milliseconds) to a [Duration] by /// splitting it into two integer components: milliseconds + microseconds. - Duration _eventTimeStampToDuration(num milliseconds) { + static Duration _eventTimeStampToDuration(num milliseconds) { final int ms = milliseconds.toInt(); final int micro = ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt(); return Duration(milliseconds: ms, microseconds: micro); } +} +mixin _WheelEventListenerMixin on _BaseAdapter { List _convertWheelEventToPointerData( html.WheelEvent event, ) { @@ -239,7 +205,7 @@ abstract class BaseAdapter { _pointerDataConverter.convert( data, change: ui.PointerChange.hover, - timeStamp: _eventTimeStampToDuration(event.timeStamp), + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.mouse, signalKind: ui.PointerSignalKind.scroll, device: _mouseDeviceId, @@ -258,116 +224,139 @@ abstract class BaseAdapter { void _addWheelEventListener(html.EventListener handler) { final dynamic eventOptions = js_util.newObject(); final html.EventListener jsHandler = js.allowInterop((html.Event event) => handler(event)); - _nativeListeners['wheel'] = jsHandler; + _BaseAdapter._nativeListeners['wheel'] = jsHandler; js_util.setProperty(eventOptions, 'passive', false); js_util.callMethod( - domRenderer.glassPaneElement, + glassPaneElement, 'addEventListener', [ 'wheel', jsHandler, eventOptions ] ); - } } -const int _kPrimaryMouseButton = 0x1; -const int _kSecondaryMouseButton = 0x2; +typedef void _PointerStateDataCallback({ + @required ui.PointerChange change, + @required int buttons, +}); -typedef _GetDeviceButton = int Function(); +class _PointerStateManager { + int _pressedButtons = 0; -int _pointerButtonFromHtmlEvent(html.Event event, {_GetDeviceButton getDefaultButton}) { - if (event is html.PointerEvent) { - final html.PointerEvent pointerEvent = event; - switch (pointerEvent.button) { - case 1: - return _kPrimaryMouseButton; - case 2: - return _kSecondaryMouseButton; - default: - break; + // Transform html.PointerEvent.buttons to Flutter's PointerEvent buttons. + int _htmlButtonsToFlutterButtons(int buttons) { + // Flutter's button definition conveniently matches that of JavaScript + // from primary button (0x1) to forward button (0x10), which allows us to + // avoid transforming it bit by bit. + return buttons & _kButtonsMask; + } + + void handleDown({@required _PointerStateDataCallback dataCallback, @required int buttons}) { + // TODO(flutter_web): Remove this temporary fix for right click + // on web platform once context guesture is implemented. + if (_pressedButtons != 0) { + _pressedButtons = 0; + dataCallback(change: ui.PointerChange.up, buttons: _pressedButtons); } - } else if (event is html.MouseEvent) { - final html.MouseEvent mouseEvent = event; - switch (mouseEvent.button) { - case 1: - return _kPrimaryMouseButton; - case 2: - return _kSecondaryMouseButton; - default: - break; + _pressedButtons = _htmlButtonsToFlutterButtons(buttons); + dataCallback(change: ui.PointerChange.down, buttons: _pressedButtons); + } + + void handleMove({@required _PointerStateDataCallback dataCallback, @required int buttons}) { + _pressedButtons = _htmlButtonsToFlutterButtons(buttons); + dataCallback( + change: _pressedButtons == 0 + ? ui.PointerChange.hover + : ui.PointerChange.move, + buttons: _pressedButtons, + ); + } + + void handleUp({@required _PointerStateDataCallback dataCallback}) { + // The pointer could have been released by a `pointerout` event, in which + // case `pointerup` should have no effect. + if (_pressedButtons == 0) { + return; } + _pressedButtons = 0; + dataCallback(change: ui.PointerChange.up, buttons: _pressedButtons); } - return getDefaultButton != null ? getDefaultButton() : _kPrimaryMouseButton; -} -int _deviceFromHtmlEvent(event) { - if (event is html.PointerEvent) { - final html.PointerEvent pointerEvent = event; - return pointerEvent.pointerId; + void handleCancel({@required _PointerStateDataCallback dataCallback}) { + _pressedButtons = 0; + dataCallback(change: ui.PointerChange.cancel, buttons: _pressedButtons); } - return _mouseDeviceId; } /// Adapter class to be used with browsers that support native pointer events. -class PointerAdapter extends BaseAdapter { +class PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerAdapter( PointerDataCallback callback, - DomRenderer domRenderer, + html.Element glassPaneElement, PointerDataConverter _pointerDataConverter - ) : super(callback, domRenderer, _pointerDataConverter); + ) : super(callback, glassPaneElement, _pointerDataConverter); + + final Map _pointerStates = {}; + + _PointerStateManager _ensurePointerState(int device) { + return _pointerStates.putIfAbsent(device, () => _PointerStateManager()); + } + + _PointerStateManager _getPointerState(int device) { + final _PointerStateManager state = _pointerStates[device]; + assert(state != null); + return state; + } @override void _setup() { _addEventListener('pointerdown', (html.Event event) { - final int device = _deviceFromHtmlEvent(event); final html.PointerEvent pointerEvent = event; - // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. - final int currentButtons = _deviceDownButtons(device); - if (currentButtons != 0) { - _setButtonDownState(device, 0); - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); - } - final int newButtons = _htmlButtonsToFlutterButtons(pointerEvent.buttons); - _setButtonDownState(device, newButtons); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + final int device = pointerEvent.pointerId; + _ensurePointerState(device).handleDown( + buttons: pointerEvent.buttons, + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); + }, + ); }); _addEventListener('pointermove', (html.Event event) { - // During a drag operation pointermove will set button to the changed - // button if there is a button change, or -1 otherwise (dragging). final html.PointerEvent pointerEvent = event; - final int device = _deviceFromHtmlEvent(event); - final int newButtons = _htmlButtonsToFlutterButtons(pointerEvent.buttons); - _setButtonDownState(device, newButtons); - _callback(_convertEventToPointerData( - _deviceDownButtons(device) == 0 - ? ui.PointerChange.hover - : ui.PointerChange.move, - pointerEvent, - )); + final int device = pointerEvent.pointerId; + final _PointerStateManager state = _ensurePointerState(device); + for (html.PointerEvent expandedEvent in _expandEvents(pointerEvent)) { + state.handleMove( + buttons: expandedEvent.buttons, + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: expandedEvent)); + }, + ); + } }); _addEventListener('pointerup', (html.Event event) { - final int device = _deviceFromHtmlEvent(event); - final int currentButtons = _deviceDownButtons(device); - _setButtonDownState(device, 0); - // The pointer could have been released by a `pointerout` event, in which - // case `pointerup` should have no effect. - if (currentButtons == 0) { - return; - } - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + final html.PointerEvent pointerEvent = event; + final int device = pointerEvent.pointerId; + _getPointerState(device).handleUp( + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); + }, + ); }); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) _addEventListener('pointercancel', (html.Event event) { - final int device = _deviceFromHtmlEvent(event); - _setButtonDownState(device, 0); - _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); + final html.PointerEvent pointerEvent = event; + final int device = pointerEvent.pointerId; + _getPointerState(device).handleCancel( + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); + }, + ); }); _addWheelEventListener((html.Event event) { @@ -390,27 +379,26 @@ class PointerAdapter extends BaseAdapter { return buttons; } - List _convertEventToPointerData( - ui.PointerChange change, - html.PointerEvent evt, - ) { + List _convertEventToPointerData({ + @required ui.PointerChange change, + @required int buttons, + @required html.PointerEvent event, + }) { final List data = []; - for (html.PointerEvent event in _expandEvents(evt)) { - _pointerDataConverter.convert( - data, - change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: _pointerTypeToDeviceKind(event.pointerType), - device: event.pointerId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: _deviceDownButtons(event.pointerId), - pressure: event.pressure, - pressureMin: 0.0, - pressureMax: 1.0, - tilt: _computeHighestTilt(event), - ); - } + _pointerDataConverter.convert( + data, + change: change, + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), + kind: _pointerTypeToDeviceKind(event.pointerType), + device: event.pointerId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: buttons, + pressure: event.pressure, + pressureMin: 0.0, + pressureMax: 1.0, + tilt: _computeHighestTilt(event), + ); return data; } @@ -450,24 +438,25 @@ class PointerAdapter extends BaseAdapter { } /// Adapter to be used with browsers that support touch events. -class TouchAdapter extends BaseAdapter { +class TouchAdapter extends _BaseAdapter { TouchAdapter( PointerDataCallback callback, - DomRenderer domRenderer, + html.Element glassPaneElement, PointerDataConverter _pointerDataConverter - ) : super(callback, domRenderer, _pointerDataConverter); + ) : super(callback, glassPaneElement, _pointerDataConverter); + + bool _pressed = false; @override void _setup() { _addEventListener('touchstart', (html.Event event) { - _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, pressed: true); + _pressed = true; _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); _addEventListener('touchmove', (html.Event event) { event.preventDefault(); // Prevents standard overscroll on iOS/Webkit. - if (!_isButtonDown(_deviceFromHtmlEvent(event), _kPrimaryMouseButton)) { + if (!_pressed) { return; } _callback(_convertEventToPointerData(ui.PointerChange.move, event)); @@ -477,12 +466,12 @@ class TouchAdapter extends BaseAdapter { // On Safari Mobile, the keyboard does not show unless this line is // added. event.preventDefault(); - _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, pressed: false); + _pressed = false; _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); _addEventListener('touchcancel', (html.Event event) { + _pressed = false; _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); } @@ -491,70 +480,66 @@ class TouchAdapter extends BaseAdapter { ui.PointerChange change, html.TouchEvent event, ) { - final html.TouchList touches = event.changedTouches; final List data = List(); - final int len = touches.length; - for (int i = 0; i < len; i++) { - final html.Touch touch = touches[i]; + for (html.Touch touch in event.changedTouches) { _pointerDataConverter.convert( data, change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.touch, signalKind: ui.PointerSignalKind.none, device: touch.identifier, physicalX: touch.client.x * ui.window.devicePixelRatio, physicalY: touch.client.y * ui.window.devicePixelRatio, + buttons: _pressed ? _kPrimaryMouseButton : 0, pressure: 1.0, pressureMin: 0.0, pressureMax: 1.0, ); } - return data; } } -/// Intentionally set to -1 so it doesn't conflict with other device IDs. -const int _mouseDeviceId = -1; - /// Adapter to be used with browsers that support mouse events. -class MouseAdapter extends BaseAdapter { +class MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { MouseAdapter( PointerDataCallback callback, - DomRenderer domRenderer, + html.Element glassPaneElement, PointerDataConverter _pointerDataConverter - ) : super(callback, domRenderer, _pointerDataConverter); + ) : super(callback, glassPaneElement, _pointerDataConverter); + + final _PointerStateManager _pointerState = _PointerStateManager(); @override void _setup() { _addEventListener('mousedown', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - if (_isButtonDown(device, pointerButton)) { - // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); - } - _updateButtonDownState(device, pointerButton, pressed: true); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + final html.MouseEvent mouseEvent = event; + _pointerState.handleDown( + buttons: mouseEvent.buttons, + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); + }, + ); }); _addEventListener('mousemove', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - final List data = _convertEventToPointerData( - _isButtonDown(device, pointerButton) - ? ui.PointerChange.move - : ui.PointerChange.hover, - event); - _callback(data); + final html.MouseEvent mouseEvent = event; + _pointerState.handleMove( + buttons: mouseEvent.buttons, + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); + }, + ); }); _addEventListener('mouseup', (html.Event event) { - final int device = _deviceFromHtmlEvent(event); - _updateButtonDownState(device, _pointerButtonFromHtmlEvent(event), pressed: false); - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + final html.MouseEvent mouseEvent = event; + _pointerState.handleUp( + dataCallback: ({ui.PointerChange change, int buttons}) { + _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); + }, + ); }); _addWheelEventListener((html.Event event) { @@ -567,15 +552,16 @@ class MouseAdapter extends BaseAdapter { }); } - List _convertEventToPointerData( - ui.PointerChange change, - html.MouseEvent event, - ) { + List _convertEventToPointerData({ + @required ui.PointerChange change, + @required int buttons, + @required html.PointerEvent event, + }) { List data = []; _pointerDataConverter.convert( data, change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.mouse, signalKind: ui.PointerSignalKind.none, device: _mouseDeviceId, From 3e1142bd29a4df9683ceb6b7f93f3677c67add92 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Dec 2019 17:35:38 -0800 Subject: [PATCH 07/28] Renames --- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- .../lib/src/engine/pointer_binding.dart | 67 ++++++++++--------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index b2fb1c102505e..074d27abe09ff 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -401,7 +401,7 @@ flt-glass-pane * { glassPaneElement .insertBefore(_accesibilityPlaceholder, _sceneHostElement); - PointerBinding(_glassPaneElement); + PointerBinding.initInstance(_glassPaneElement); // Hide the DOM nodes used to render the scene from accessibility, because // the accessibility tree is built from the SemanticsNode tree as a parallel diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index f760c22576c00..4ee3000c6fe08 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -8,7 +8,7 @@ part of engine; const bool _debugLogPointerEvents = false; /// The signature of a callback that handles pointer events. -typedef PointerDataCallback = void Function(List); +typedef _PointerDataCallback = void Function(List); // The mask for the bitfield of event buttons. Buttons not contained in this // mask are cut off. @@ -27,26 +27,31 @@ class PointerBinding { static PointerBinding get instance => _instance; static PointerBinding _instance; - PointerBinding(this.glassPaneElement) { + static void initInstance(html.Element glassPaneElement) { if (_instance == null) { - _instance = this; - _pointerDataConverter = PointerDataConverter(); - _detector = const PointerSupportDetector(); - _adapter = _createAdapter(); + _instance = PointerBinding._(glassPaneElement); + assert(() { + registerHotRestartListener(() { + _instance._adapter?.clearListeners(); + _instance._pointerDataConverter?.clearPointerState(); + }); + return true; + }()); } - assert(() { - registerHotRestartListener(() { - _adapter?.clearListeners(); - _pointerDataConverter?.clearPointerState(); - }); - return true; - }()); + } + + PointerBinding._(this.glassPaneElement) { + _pointerDataConverter = PointerDataConverter(); + _detector = const PointerSupportDetector(); + _adapter = _createAdapter(); } final html.Element glassPaneElement; + PointerSupportDetector _detector; _BaseAdapter _adapter; PointerDataConverter _pointerDataConverter; + /// Should be used in tests to define custom detection of pointer support. /// /// ```dart @@ -77,13 +82,13 @@ class PointerBinding { _BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasTouchEvents) { - return TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasMouseEvents) { - return MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } return null; } @@ -112,7 +117,7 @@ class PointerSupportDetector { /// Common functionality that's shared among adapters. abstract class _BaseAdapter { _BaseAdapter(this._callback, this.glassPaneElement, this._pointerDataConverter) { - _setup(); + setup(); } /// Listeners that are registered through dart to js api. @@ -122,12 +127,12 @@ abstract class _BaseAdapter { static final Map _nativeListeners = {}; final html.Element glassPaneElement; - PointerDataCallback _callback; + _PointerDataCallback _callback; PointerDataConverter _pointerDataConverter; /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. - void _setup(); + void setup(); /// Remove all active event listeners. void clearListeners() { @@ -291,9 +296,9 @@ class _PointerStateManager { } /// Adapter class to be used with browsers that support native pointer events. -class PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { - PointerAdapter( - PointerDataCallback callback, +class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { + _PointerAdapter( + _PointerDataCallback callback, html.Element glassPaneElement, PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); @@ -311,7 +316,7 @@ class PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } @override - void _setup() { + void setup() { _addEventListener('pointerdown', (html.Event event) { final html.PointerEvent pointerEvent = event; final int device = pointerEvent.pointerId; @@ -438,9 +443,9 @@ class PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } /// Adapter to be used with browsers that support touch events. -class TouchAdapter extends _BaseAdapter { - TouchAdapter( - PointerDataCallback callback, +class _TouchAdapter extends _BaseAdapter { + _TouchAdapter( + _PointerDataCallback callback, html.Element glassPaneElement, PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); @@ -448,7 +453,7 @@ class TouchAdapter extends _BaseAdapter { bool _pressed = false; @override - void _setup() { + void setup() { _addEventListener('touchstart', (html.Event event) { _pressed = true; _callback(_convertEventToPointerData(ui.PointerChange.down, event)); @@ -502,9 +507,9 @@ class TouchAdapter extends _BaseAdapter { } /// Adapter to be used with browsers that support mouse events. -class MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { - MouseAdapter( - PointerDataCallback callback, +class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { + _MouseAdapter( + _PointerDataCallback callback, html.Element glassPaneElement, PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); @@ -512,7 +517,7 @@ class MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final _PointerStateManager _pointerState = _PointerStateManager(); @override - void _setup() { + void setup() { _addEventListener('mousedown', (html.Event event) { final html.MouseEvent mouseEvent = event; _pointerState.handleDown( From 199b709d3038e5b0f760856e3d872934c1c4672d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Dec 2019 17:46:46 -0800 Subject: [PATCH 08/28] Remove test function --- lib/web_ui/test/engine/pointer_binding_test.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 19a563a3ee09f..9972fffcf6c40 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -782,16 +782,6 @@ void main() { }); } -void printPackets(List packets) { - print('packets ${packets.length}'); - for (ui.PointerDataPacket packet in packets) { - print('packet'); - for (ui.PointerData data in packet.data) { - print('data ${data.change} ${data.device} ${data.buttons} (${data.physicalX}, ${data.physicalY}) ${data.synthesized}'); - } - } -} - class TestPointerDetector extends PointerSupportDetector { @override final bool hasPointerEvents = true; From 19dcf7ddcaf8155d0fc654f0c22f8ae26c0bd394 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 10:49:34 -0800 Subject: [PATCH 09/28] Rewrite using expand --- .../lib/src/engine/pointer_binding.dart | 190 ++++++++++-------- .../test/engine/pointer_binding_test.dart | 4 +- 2 files changed, 113 insertions(+), 81 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 4ee3000c6fe08..179820cd48dff 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -8,7 +8,7 @@ part of engine; const bool _debugLogPointerEvents = false; /// The signature of a callback that handles pointer events. -typedef _PointerDataCallback = void Function(List); +typedef _PointerDataCallback = void Function(Iterable); // The mask for the bitfield of event buttons. Buttons not contained in this // mask are cut off. @@ -93,8 +93,8 @@ class PointerBinding { return null; } - void _onPointerData(List data) { - final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data); + void _onPointerData(Iterable data) { + final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList()); final ui.PointerDataPacketCallback callback = ui.window.onPointerDataPacket; if (callback != null) { callback(packet); @@ -242,12 +242,21 @@ mixin _WheelEventListenerMixin on _BaseAdapter { } } -typedef void _PointerStateDataCallback({ - @required ui.PointerChange change, - @required int buttons, -}); +@immutable +class _SanitizedDetails { + const _SanitizedDetails({ + @required this.buttons, + @required this.change, + }); -class _PointerStateManager { + final ui.PointerChange change; + final int buttons; + + @override + String toString() => '$runtimeType(change: $change, buttons: $buttons)'; +} + +class _PointerEventSanitizer { int _pressedButtons = 0; // Transform html.PointerEvent.buttons to Flutter's PointerEvent buttons. @@ -258,40 +267,54 @@ class _PointerStateManager { return buttons & _kButtonsMask; } - void handleDown({@required _PointerStateDataCallback dataCallback, @required int buttons}) { + List<_SanitizedDetails> sanitizeDownEvent({@required int buttons}) { + final List<_SanitizedDetails> result = <_SanitizedDetails>[]; // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. + // on web platform once context gesture is implemented. if (_pressedButtons != 0) { _pressedButtons = 0; - dataCallback(change: ui.PointerChange.up, buttons: _pressedButtons); + result.add(_SanitizedDetails( + change: ui.PointerChange.up, + buttons: 0, + )); } _pressedButtons = _htmlButtonsToFlutterButtons(buttons); - dataCallback(change: ui.PointerChange.down, buttons: _pressedButtons); + result.add(_SanitizedDetails( + change: ui.PointerChange.down, + buttons: _pressedButtons, + )); + return result; } - void handleMove({@required _PointerStateDataCallback dataCallback, @required int buttons}) { + List<_SanitizedDetails> sanitizeMoveEvent({@required int buttons}) { _pressedButtons = _htmlButtonsToFlutterButtons(buttons); - dataCallback( + return <_SanitizedDetails>[_SanitizedDetails( change: _pressedButtons == 0 ? ui.PointerChange.hover : ui.PointerChange.move, buttons: _pressedButtons, - ); + )]; } - void handleUp({@required _PointerStateDataCallback dataCallback}) { + List<_SanitizedDetails> sanitizeUpEvent() { // The pointer could have been released by a `pointerout` event, in which // case `pointerup` should have no effect. if (_pressedButtons == 0) { - return; + return <_SanitizedDetails>[]; } _pressedButtons = 0; - dataCallback(change: ui.PointerChange.up, buttons: _pressedButtons); + return <_SanitizedDetails>[_SanitizedDetails( + change: ui.PointerChange.up, + buttons: _pressedButtons, + )]; } - void handleCancel({@required _PointerStateDataCallback dataCallback}) { + List<_SanitizedDetails> sanitizeCancelEvent() { _pressedButtons = 0; - dataCallback(change: ui.PointerChange.cancel, buttons: _pressedButtons); + return <_SanitizedDetails>[_SanitizedDetails( + change: ui.PointerChange.cancel, + buttons: _pressedButtons, + )]; } } @@ -303,14 +326,14 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - final Map _pointerStates = {}; + final Map _pointerStates = {}; - _PointerStateManager _ensurePointerState(int device) { - return _pointerStates.putIfAbsent(device, () => _PointerStateManager()); + _PointerEventSanitizer _ensurePointerState(int device) { + return _pointerStates.putIfAbsent(device, () => _PointerEventSanitizer()); } - _PointerStateManager _getPointerState(int device) { - final _PointerStateManager state = _pointerStates[device]; + _PointerEventSanitizer _getPointerState(int device) { + final _PointerEventSanitizer state = _pointerStates[device]; assert(state != null); return state; } @@ -320,35 +343,39 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addEventListener('pointerdown', (html.Event event) { final html.PointerEvent pointerEvent = event; final int device = pointerEvent.pointerId; - _ensurePointerState(device).handleDown( - buttons: pointerEvent.buttons, - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); - }, + _callback( + _ensurePointerState(device) + .sanitizeDownEvent(buttons: pointerEvent.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: pointerEvent, details: details), + ), ); }); _addEventListener('pointermove', (html.Event event) { final html.PointerEvent pointerEvent = event; final int device = pointerEvent.pointerId; - final _PointerStateManager state = _ensurePointerState(device); - for (html.PointerEvent expandedEvent in _expandEvents(pointerEvent)) { - state.handleMove( - buttons: expandedEvent.buttons, - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: expandedEvent)); - }, - ); - } + final _PointerEventSanitizer state = _ensurePointerState(device); + _callback( + _expandEvents(pointerEvent) + .expand((html.PointerEvent expandedEvent) => + state.sanitizeMoveEvent(buttons: expandedEvent.buttons), + ) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: pointerEvent, details: details), + ), + ); }); _addEventListener('pointerup', (html.Event event) { final html.PointerEvent pointerEvent = event; final int device = pointerEvent.pointerId; - _getPointerState(device).handleUp( - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); - }, + _callback( + _getPointerState(device) + .sanitizeUpEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: pointerEvent, details: details), + ), ); }); @@ -357,10 +384,12 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addEventListener('pointercancel', (html.Event event) { final html.PointerEvent pointerEvent = event; final int device = pointerEvent.pointerId; - _getPointerState(device).handleCancel( - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: pointerEvent)); - }, + _callback( + _getPointerState(device) + .sanitizeCancelEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: pointerEvent, details: details), + ), ); }); @@ -376,29 +405,20 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { }); } - // Transform html.PointerEvent.buttons to Flutter's PointerEvent buttons. - int _htmlButtonsToFlutterButtons(int buttons) { - // Flutter's button definition conveniently matches that of JavaScript - // from primary button (0x1) to forward button (0x10), which allows us to - // avoid transforming it bit by bit. - return buttons; - } - List _convertEventToPointerData({ - @required ui.PointerChange change, - @required int buttons, @required html.PointerEvent event, + @required _SanitizedDetails details, }) { final List data = []; _pointerDataConverter.convert( data, - change: change, + change: details.change, timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: _pointerTypeToDeviceKind(event.pointerType), device: event.pointerId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: buttons, + buttons: details.buttons, pressure: event.pressure, pressureMin: 0.0, pressureMax: 1.0, @@ -514,36 +534,40 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - final _PointerStateManager _pointerState = _PointerStateManager(); + final _PointerEventSanitizer _sanitizer = _PointerEventSanitizer(); @override void setup() { _addEventListener('mousedown', (html.Event event) { final html.MouseEvent mouseEvent = event; - _pointerState.handleDown( - buttons: mouseEvent.buttons, - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); - }, + _callback( + _sanitizer + .sanitizeDownEvent(buttons: mouseEvent.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: mouseEvent, details: details), + ), ); }); _addEventListener('mousemove', (html.Event event) { final html.MouseEvent mouseEvent = event; - _pointerState.handleMove( - buttons: mouseEvent.buttons, - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); - }, - ); + _callback( + _sanitizer + .sanitizeMoveEvent(buttons: mouseEvent.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: mouseEvent, details: details), + ), + ); }); _addEventListener('mouseup', (html.Event event) { final html.MouseEvent mouseEvent = event; - _pointerState.handleUp( - dataCallback: ({ui.PointerChange change, int buttons}) { - _callback(_convertEventToPointerData(change: change, buttons: buttons, event: mouseEvent)); - }, + _callback( + _sanitizer + .sanitizeUpEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: mouseEvent, details: details), + ), ); }); @@ -554,25 +578,33 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { } _callback(_convertWheelEventToPointerData(event)); event.preventDefault(); + + final html.MouseEvent mouseEvent = event; + final List convertedData = []; + for(_SanitizedDetails details in _sanitizer.sanitizeUpEvent()) { + convertedData.addAll(_convertEventToPointerData(event: mouseEvent, details: details)); + } + _callback(convertedData); }); } List _convertEventToPointerData({ - @required ui.PointerChange change, - @required int buttons, @required html.PointerEvent event, + @required _SanitizedDetails details, }) { + assert(event != null); + assert(details != null); List data = []; _pointerDataConverter.convert( data, - change: change, + change: details.change, timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.mouse, signalKind: ui.PointerSignalKind.none, device: _mouseDeviceId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, + buttons: details.buttons, pressure: 1.0, pressureMin: 0.0, pressureMax: 1.0, diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 9972fffcf6c40..d4ede918922a1 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -56,14 +56,14 @@ void main() { 'buttons': 1, })); - expect(packets, hasLength(3)); + expect(packets, hasLength(2)); // An add will be synthesized. expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); expect(packets[0].data[0].synthesized, equals(true)); expect(packets[0].data[1].change, equals(ui.PointerChange.down)); expect(packets[1].data[0].change, equals(ui.PointerChange.up)); - expect(packets[2].data[0].change, equals(ui.PointerChange.down)); + expect(packets[1].data[1].change, equals(ui.PointerChange.down)); }); test('does not synthesize pointer up if from different device', () { From 9b678a94727cb48ead4ab7558910d48775bdbf3a Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 11:09:47 -0800 Subject: [PATCH 10/28] Add listener utility method --- .../lib/src/engine/pointer_binding.dart | 153 +++++++++--------- 1 file changed, 79 insertions(+), 74 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 179820cd48dff..cb7cf1ba1ee41 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -154,7 +154,7 @@ abstract class _BaseAdapter { _nativeListeners.clear(); } - void _addEventListener(String eventName, html.EventListener handler) { + void addEventListener(String eventName, html.EventListener handler) { final html.EventListener loggedHandler = (html.Event event) { if (_debugLogPointerEvents) { print(event.type); @@ -318,6 +318,8 @@ class _PointerEventSanitizer { } } +typedef _PointerEventListener = dynamic Function(html.PointerEvent event); + /// Adapter class to be used with browsers that support native pointer events. class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _PointerAdapter( @@ -338,59 +340,59 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { return state; } + void _addPointerEventListener(String eventName, _PointerEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.PointerEvent pointerEvent = event; + return handler(pointerEvent); + }); + } + @override void setup() { - _addEventListener('pointerdown', (html.Event event) { - final html.PointerEvent pointerEvent = event; - final int device = pointerEvent.pointerId; - _callback( - _ensurePointerState(device) - .sanitizeDownEvent(buttons: pointerEvent.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: pointerEvent, details: details), - ), - ); + _addPointerEventListener('pointerdown', (html.PointerEvent event) { + final int device = event.pointerId; + final Iterable pointerData = _ensurePointerState(device) + .sanitizeDownEvent(buttons: event.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); - _addEventListener('pointermove', (html.Event event) { - final html.PointerEvent pointerEvent = event; - final int device = pointerEvent.pointerId; + _addPointerEventListener('pointermove', (html.PointerEvent event) { + final int device = event.pointerId; final _PointerEventSanitizer state = _ensurePointerState(device); - _callback( - _expandEvents(pointerEvent) - .expand((html.PointerEvent expandedEvent) => - state.sanitizeMoveEvent(buttons: expandedEvent.buttons), - ) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: pointerEvent, details: details), - ), - ); + final Iterable pointerData = _expandEvents(event) + .expand((html.PointerEvent expandedEvent) { + return state + .sanitizeMoveEvent(buttons: expandedEvent.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: expandedEvent, details: details), + ); + }); + _callback(pointerData); }); - _addEventListener('pointerup', (html.Event event) { - final html.PointerEvent pointerEvent = event; - final int device = pointerEvent.pointerId; - _callback( - _getPointerState(device) - .sanitizeUpEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: pointerEvent, details: details), - ), - ); + _addPointerEventListener('pointerup', (html.PointerEvent event) { + final int device = event.pointerId; + final Iterable pointerData = _getPointerState(device) + .sanitizeUpEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) - _addEventListener('pointercancel', (html.Event event) { - final html.PointerEvent pointerEvent = event; - final int device = pointerEvent.pointerId; - _callback( - _getPointerState(device) - .sanitizeCancelEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: pointerEvent, details: details), - ), - ); + _addPointerEventListener('pointercancel', (html.PointerEvent event) { + final int device = event.pointerId; + final Iterable pointerData = _getPointerState(device) + .sanitizeCancelEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); _addWheelEventListener((html.Event event) { @@ -474,12 +476,12 @@ class _TouchAdapter extends _BaseAdapter { @override void setup() { - _addEventListener('touchstart', (html.Event event) { + addEventListener('touchstart', (html.Event event) { _pressed = true; _callback(_convertEventToPointerData(ui.PointerChange.down, event)); }); - _addEventListener('touchmove', (html.Event event) { + addEventListener('touchmove', (html.Event event) { event.preventDefault(); // Prevents standard overscroll on iOS/Webkit. if (!_pressed) { return; @@ -487,7 +489,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(_convertEventToPointerData(ui.PointerChange.move, event)); }); - _addEventListener('touchend', (html.Event event) { + addEventListener('touchend', (html.Event event) { // On Safari Mobile, the keyboard does not show unless this line is // added. event.preventDefault(); @@ -495,7 +497,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); - _addEventListener('touchcancel', (html.Event event) { + addEventListener('touchcancel', (html.Event event) { _pressed = false; _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); @@ -526,6 +528,8 @@ class _TouchAdapter extends _BaseAdapter { } } +typedef _MouseEventListener = dynamic Function(html.MouseEvent event); + /// Adapter to be used with browsers that support mouse events. class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _MouseAdapter( @@ -536,39 +540,40 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final _PointerEventSanitizer _sanitizer = _PointerEventSanitizer(); + void _addMouseEventListener(String eventName, _MouseEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.MouseEvent mouseEvent = event; + return handler(mouseEvent); + }); + } + @override void setup() { - _addEventListener('mousedown', (html.Event event) { - final html.MouseEvent mouseEvent = event; - _callback( - _sanitizer - .sanitizeDownEvent(buttons: mouseEvent.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: mouseEvent, details: details), - ), - ); + _addMouseEventListener('mousedown', (html.MouseEvent event) { + final Iterable pointerData = _sanitizer + .sanitizeDownEvent(buttons: event.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); - _addEventListener('mousemove', (html.Event event) { - final html.MouseEvent mouseEvent = event; - _callback( - _sanitizer - .sanitizeMoveEvent(buttons: mouseEvent.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: mouseEvent, details: details), - ), - ); + _addMouseEventListener('mousemove', (html.MouseEvent event) { + final Iterable pointerData = _sanitizer + .sanitizeMoveEvent(buttons: event.buttons) + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); - _addEventListener('mouseup', (html.Event event) { - final html.MouseEvent mouseEvent = event; - _callback( - _sanitizer - .sanitizeUpEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: mouseEvent, details: details), - ), - ); + _addMouseEventListener('mouseup', (html.MouseEvent event) { + final Iterable pointerData = _sanitizer + .sanitizeUpEvent() + .expand((_SanitizedDetails details) => + _convertEventToPointerData(event: event, details: details), + ); + _callback(pointerData); }); _addWheelEventListener((html.Event event) { From 6051260e15232017bf0e5d22574c59e470277515 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 13:51:00 -0800 Subject: [PATCH 11/28] Add test for Mouse --- .../lib/src/engine/pointer_binding.dart | 12 +- .../engine/pointer_binding_mouse_test.dart | 787 ++++++++++++++++++ ...dart => pointer_binding_pointer_test.dart} | 2 +- 3 files changed, 792 insertions(+), 9 deletions(-) create mode 100644 lib/web_ui/test/engine/pointer_binding_mouse_test.dart rename lib/web_ui/test/engine/{pointer_binding_test.dart => pointer_binding_pointer_test.dart} (99%) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index cb7cf1ba1ee41..078a9b7ae1690 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -417,6 +417,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { change: details.change, timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: _pointerTypeToDeviceKind(event.pointerType), + signalKind: ui.PointerSignalKind.none, device: event.pointerId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, @@ -582,19 +583,14 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { print(event.type); } _callback(_convertWheelEventToPointerData(event)); + // Prevent default so mouse wheel event doesn't get converted to + // a scroll event that semantic nodes would process. event.preventDefault(); - - final html.MouseEvent mouseEvent = event; - final List convertedData = []; - for(_SanitizedDetails details in _sanitizer.sanitizeUpEvent()) { - convertedData.addAll(_convertEventToPointerData(event: mouseEvent, details: details)); - } - _callback(convertedData); }); } List _convertEventToPointerData({ - @required html.PointerEvent event, + @required html.MouseEvent event, @required _SanitizedDetails details, }) { assert(event != null); diff --git a/lib/web_ui/test/engine/pointer_binding_mouse_test.dart b/lib/web_ui/test/engine/pointer_binding_mouse_test.dart new file mode 100644 index 0000000000000..38e83c388bd08 --- /dev/null +++ b/lib/web_ui/test/engine/pointer_binding_mouse_test.dart @@ -0,0 +1,787 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:html' as html; +import 'dart:js_util' as js_util; + +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'package:test/test.dart'; + +void main() { + group('Pointer Binding', () { + html.Element glassPane = domRenderer.glassPaneElement; + + setUp(() { + // Touching domRenderer creates PointerBinding.instance. + domRenderer; + + // Set a new detector to reset the state of the listeners. + PointerBinding.instance.debugOverrideDetector(TestPointerDetector()); + + ui.window.onPointerDataPacket = null; + }); + + test('can receive pointer events on the glass pane', () { + ui.PointerDataPacket receivedPacket; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + receivedPacket = packet; + }; + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + )); + + expect(receivedPacket, isNotNull); + expect(receivedPacket.data[0].device, equals(-1)); + }); + + test('synthesizes a pointerup event on two pointerdowns in a row', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + )); + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + )); + + expect(packets, hasLength(2)); + // An add will be synthesized. + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[1].data[0].change, equals(ui.PointerChange.up)); + expect(packets[1].data[1].change, equals(ui.PointerChange.down)); + }); + + test('creates an add event if the first pointer activity is a hover', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 0, + )); + + expect(packets, hasLength(1)); + expect(packets.single.data, hasLength(2)); + + expect(packets.single.data[0].change, equals(ui.PointerChange.add)); + expect(packets.single.data[0].synthesized, equals(true)); + expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); + }); + + test('does create an add event if got a pointerdown', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + )); + + expect(packets, hasLength(1)); + expect(packets.single.data, hasLength(2)); + + expect(packets.single.data[0].change, equals(ui.PointerChange.add)); + expect(packets.single.data[1].change, equals(ui.PointerChange.down)); + }); + + test('does calculate delta and pointer identifier correctly', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 0, + clientX: 10.0, + clientY: 10.0, + )); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 0, + clientX: 20.0, + clientY: 20.0, + )); + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + clientX: 20.0, + clientY: 20.0, + )); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 1, + clientX: 40.0, + clientY: 30.0, + )); + + glassPane.dispatchEvent(_upEvent( + button: 0, + buttons: 0, + clientX: 40.0, + clientY: 30.0, + )); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 0, + clientX: 20.0, + clientY: 10.0, + )); + + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + clientX: 20.0, + clientY: 10.0, + )); + + expect(packets, hasLength(7)); + + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(0)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].pointerIdentifier, equals(0)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10.0)); + expect(packets[0].data[1].physicalY, equals(10.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + + expect(packets[1].data, hasLength(1)); + expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[1].data[0].pointerIdentifier, equals(0)); + expect(packets[1].data[0].synthesized, equals(false)); + expect(packets[1].data[0].physicalX, equals(20.0)); + expect(packets[1].data[0].physicalY, equals(20.0)); + expect(packets[1].data[0].physicalDeltaX, equals(10.0)); + expect(packets[1].data[0].physicalDeltaY, equals(10.0)); + + expect(packets[2].data, hasLength(1)); + expect(packets[2].data[0].change, equals(ui.PointerChange.down)); + expect(packets[2].data[0].pointerIdentifier, equals(1)); + expect(packets[2].data[0].synthesized, equals(false)); + expect(packets[2].data[0].physicalX, equals(20.0)); + expect(packets[2].data[0].physicalY, equals(20.0)); + expect(packets[2].data[0].physicalDeltaX, equals(0.0)); + expect(packets[2].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[3].data, hasLength(1)); + expect(packets[3].data[0].change, equals(ui.PointerChange.move)); + expect(packets[3].data[0].pointerIdentifier, equals(1)); + expect(packets[3].data[0].synthesized, equals(false)); + expect(packets[3].data[0].physicalX, equals(40.0)); + expect(packets[3].data[0].physicalY, equals(30.0)); + expect(packets[3].data[0].physicalDeltaX, equals(20.0)); + expect(packets[3].data[0].physicalDeltaY, equals(10.0)); + + expect(packets[4].data, hasLength(1)); + expect(packets[4].data[0].change, equals(ui.PointerChange.up)); + expect(packets[4].data[0].pointerIdentifier, equals(1)); + expect(packets[4].data[0].synthesized, equals(false)); + expect(packets[4].data[0].physicalX, equals(40.0)); + expect(packets[4].data[0].physicalY, equals(30.0)); + expect(packets[4].data[0].physicalDeltaX, equals(0.0)); + expect(packets[4].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[5].data, hasLength(1)); + expect(packets[5].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[5].data[0].pointerIdentifier, equals(1)); + expect(packets[5].data[0].synthesized, equals(false)); + expect(packets[5].data[0].physicalX, equals(20.0)); + expect(packets[5].data[0].physicalY, equals(10.0)); + expect(packets[5].data[0].physicalDeltaX, equals(-20.0)); + expect(packets[5].data[0].physicalDeltaY, equals(-20.0)); + + expect(packets[6].data, hasLength(1)); + expect(packets[6].data[0].change, equals(ui.PointerChange.down)); + expect(packets[6].data[0].pointerIdentifier, equals(2)); + expect(packets[6].data[0].synthesized, equals(false)); + expect(packets[6].data[0].physicalX, equals(20.0)); + expect(packets[6].data[0].physicalY, equals(10.0)); + expect(packets[6].data[0].physicalDeltaX, equals(0.0)); + expect(packets[6].data[0].physicalDeltaY, equals(0.0)); + }); + + test('does synthesize add or hover or more for scroll', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(html.WheelEvent('wheel', + button: 1, + clientX: 10, + clientY: 10, + deltaX: 10, + deltaY: 10, + )); + + glassPane.dispatchEvent(html.WheelEvent('wheel', + button: 1, + clientX: 20, + clientY: 50, + deltaX: 10, + deltaY: 10, + )); + + glassPane.dispatchEvent(_downEvent( + button: 1, + clientX: 20.0, + clientY: 50.0, + )); + + glassPane.dispatchEvent(html.WheelEvent('wheel', + button: 1, + clientX: 30, + clientY: 60, + deltaX: 10, + deltaY: 10, + )); + + expect(packets, hasLength(4)); + + // An add will be synthesized. + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(0)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[0].data[1].pointerIdentifier, equals(0)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10.0)); + expect(packets[0].data[1].physicalY, equals(10.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + + // A hover will be synthesized. + expect(packets[1].data, hasLength(2)); + expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[1].data[0].pointerIdentifier, equals(0)); + expect(packets[1].data[0].synthesized, equals(true)); + expect(packets[1].data[0].physicalX, equals(20.0)); + expect(packets[1].data[0].physicalY, equals(50.0)); + expect(packets[1].data[0].physicalDeltaX, equals(10.0)); + expect(packets[1].data[0].physicalDeltaY, equals(40.0)); + + expect(packets[1].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[1].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[1].data[1].pointerIdentifier, equals(0)); + expect(packets[1].data[1].synthesized, equals(false)); + expect(packets[1].data[1].physicalX, equals(20.0)); + expect(packets[1].data[1].physicalY, equals(50.0)); + expect(packets[1].data[1].physicalDeltaX, equals(0.0)); + expect(packets[1].data[1].physicalDeltaY, equals(0.0)); + + // No synthetic pointer data for down event. + expect(packets[2].data, hasLength(1)); + expect(packets[2].data[0].change, equals(ui.PointerChange.down)); + expect(packets[2].data[0].signalKind, equals(ui.PointerSignalKind.none)); + expect(packets[2].data[0].pointerIdentifier, equals(1)); + expect(packets[2].data[0].synthesized, equals(false)); + expect(packets[2].data[0].physicalX, equals(20.0)); + expect(packets[2].data[0].physicalY, equals(50.0)); + expect(packets[2].data[0].physicalDeltaX, equals(0.0)); + expect(packets[2].data[0].physicalDeltaY, equals(0.0)); + + // A move will be synthesized instead of hover because the button is currently down. + expect(packets[3].data, hasLength(2)); + expect(packets[3].data[0].change, equals(ui.PointerChange.move)); + expect(packets[3].data[0].pointerIdentifier, equals(1)); + expect(packets[3].data[0].synthesized, equals(true)); + expect(packets[3].data[0].physicalX, equals(30.0)); + expect(packets[3].data[0].physicalY, equals(60.0)); + expect(packets[3].data[0].physicalDeltaX, equals(10.0)); + expect(packets[3].data[0].physicalDeltaY, equals(10.0)); + + expect(packets[3].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[3].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[3].data[1].pointerIdentifier, equals(1)); + expect(packets[3].data[1].synthesized, equals(false)); + expect(packets[3].data[1].physicalX, equals(30.0)); + expect(packets[3].data[1].physicalY, equals(60.0)); + expect(packets[3].data[1].physicalDeltaX, equals(0.0)); + expect(packets[3].data[1].physicalDeltaY, equals(0.0)); + }); + + test('correctly converts buttons of down, move and up events', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Add and hover + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 0, + clientX: 10, + clientY: 11, + )); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10)); + expect(packets[0].data[1].physicalY, equals(11)); + expect(packets[0].data[1].buttons, equals(0)); + packets.clear(); + + // Drag with primary button + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + clientX: 10.0, + clientY: 11.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + expect(packets[0].data[0].buttons, equals(1)); + packets.clear(); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 1, + clientX: 20.0, + clientY: 21.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(1)); + packets.clear(); + + glassPane.dispatchEvent(_upEvent( + button: 0, + buttons: 0, + clientX: 20.0, + clientY: 21.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + + // Drag with secondary button + glassPane.dispatchEvent(_downEvent( + button: 2, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 2, + clientX: 30.0, + clientY: 31.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(_upEvent( + button: 2, + buttons: 0, + clientX: 30.0, + clientY: 31.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + + // Drag with middle button + glassPane.dispatchEvent(_downEvent( + button: 1, + buttons: 4, + clientX: 30.0, + clientY: 31.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(30)); + expect(packets[0].data[0].physicalY, equals(31)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 4, + clientX: 40.0, + clientY: 41.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40)); + expect(packets[0].data[0].physicalY, equals(41)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); + + glassPane.dispatchEvent(_upEvent( + button: 1, + buttons: 0, + clientX: 40.0, + clientY: 41.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40)); + expect(packets[0].data[0].physicalY, equals(41)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + + test('correctly handles button changes during a down sequence', () { + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Press LMB. + glassPane.dispatchEvent(_downEvent( + button: 0, + buttons: 1, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].buttons, equals(1)); + packets.clear(); + + // Press MMB. + glassPane.dispatchEvent(_moveEvent( + button: 1, + buttons: 5, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(5)); + packets.clear(); + + // Release LMB. + glassPane.dispatchEvent(_moveEvent( + button: 0, + buttons: 4, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(4)); + packets.clear(); + + // Release MMB. + glassPane.dispatchEvent(_upEvent( + button: 1, + buttons: 0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + + test('synthesizes a pointerup event when pointermove comes before the up', () { + // This can happen when the user pops up the context menu by right + // clicking, then dismisses it with a left click. + + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(_downEvent( + button: 2, + buttons: 2, + clientX: 10, + clientY: 11, + )); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10)); + expect(packets[0].data[0].physicalY, equals(11)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10)); + expect(packets[0].data[1].physicalY, equals(11)); + expect(packets[0].data[1].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + glassPane.dispatchEvent(_upEvent( + button: 2, + buttons: 0, + clientX: 20.0, + clientY: 21.0, + )); + + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20)); + expect(packets[0].data[0].physicalY, equals(21)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + + test('correctly handles uncontinuous button changes during a down sequence', () { + // This can happen with the following gesture sequence: + // + // - Pops up the context menu by right clicking, but holds RMB; + // - Clicks LMB; + // - Releases RMB. + + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Press RMB and hold, popping up the context menu. + glassPane.dispatchEvent(_downEvent( + button: 2, + buttons: 2, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].buttons, equals(2)); + packets.clear(); + + // Press LMB. The event will have "button: -1" here, despite the change + // in "buttons", probably because the "press" gesture was absorbed by + // dismissing the context menu. + glassPane.dispatchEvent(_moveEvent( + button: -1, + buttons: 3, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(3)); + packets.clear(); + + // Release LMB. + glassPane.dispatchEvent(_moveEvent( + button: 0, + buttons: 2, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(2)); + packets.clear(); + + // Release RMB. + glassPane.dispatchEvent(_upEvent( + button: 2, + buttons: 0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].buttons, equals(0)); + packets.clear(); + }); + }); +} + +class TestPointerDetector extends PointerSupportDetector { + @override + final bool hasPointerEvents = false; + + @override + final bool hasTouchEvents = false; + + @override + final bool hasMouseEvents = true; +} + +html.MouseEvent _createMouseEvent( + String type, { + int buttons, + int button, + double clientX, + double clientY, +}) { + final Function jsMouseEvent = js_util.getProperty(html.window, 'MouseEvent'); + final List eventArgs = [ + type, + { + 'buttons': buttons, + 'button': button, + 'clientX': clientX, + 'clientY': clientY, + } + ]; + return js_util.callConstructor(jsMouseEvent, js_util.jsify(eventArgs)); +} + +html.MouseEvent _downEvent({ + int buttons, + int button, + double clientX = 0, + double clientY = 0, +}) { + return _createMouseEvent( + 'mousedown', + buttons: buttons, + button: button, + clientX: clientX, + clientY: clientY, + ); +} + +html.MouseEvent _moveEvent({ + int buttons, + int button, + double clientX = 0, + double clientY = 0, +}) { + return _createMouseEvent( + 'mousemove', + buttons: buttons, + button: button, + clientX: clientX, + clientY: clientY, + ); +} + +html.MouseEvent _upEvent({ + int buttons = 0, + int button, + double clientX = 0, + double clientY = 0, +}) { + return _createMouseEvent( + 'mouseup', + buttons: buttons, + button: button, + clientX: clientX, + clientY: clientY, + ); +} diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_pointer_test.dart similarity index 99% rename from lib/web_ui/test/engine/pointer_binding_test.dart rename to lib/web_ui/test/engine/pointer_binding_pointer_test.dart index d4ede918922a1..c429c485ec256 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_pointer_test.dart @@ -356,7 +356,7 @@ void main() { // No synthetic pointer data for down event. expect(packets[2].data, hasLength(1)); expect(packets[2].data[0].change, equals(ui.PointerChange.down)); - expect(packets[2].data[0].signalKind, equals(null)); + expect(packets[2].data[0].signalKind, equals(ui.PointerSignalKind.none)); expect(packets[2].data[0].pointerIdentifier, equals(1)); expect(packets[2].data[0].synthesized, equals(false)); expect(packets[2].data[0].physicalX, equals(20.0)); From 4baa5bac713f766cd6e08635591447a3bc28fcf1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 18:38:41 -0800 Subject: [PATCH 12/28] Refactor to context --- .../lib/src/engine/pointer_binding.dart | 13 +- .../engine/pointer_binding_pointer_test.dart | 683 ++++++++++-------- 2 files changed, 403 insertions(+), 293 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 078a9b7ae1690..6e4920503c8ab 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -183,8 +183,8 @@ abstract class _BaseAdapter { mixin _WheelEventListenerMixin on _BaseAdapter { List _convertWheelEventToPointerData( - html.WheelEvent event, - ) { + html.WheelEvent event + ) { const int domDeltaPixel = 0x00; const int domDeltaLine = 0x01; const int domDeltaPage = 0x02; @@ -412,13 +412,18 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { @required _SanitizedDetails details, }) { final List data = []; + final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType); + // We force `device: _mouseDeviceId` on mouse pointers because Wheel events + // might come before any PointerEvents, and since wheel events don't contain + // pointerId we always assign `device: _mouseDeviceId` to them. + final int device = kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : event.pointerId; _pointerDataConverter.convert( data, change: details.change, timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), - kind: _pointerTypeToDeviceKind(event.pointerType), + kind: kind, signalKind: ui.PointerSignalKind.none, - device: event.pointerId, + device: device, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, buttons: details.buttons, diff --git a/lib/web_ui/test/engine/pointer_binding_pointer_test.dart b/lib/web_ui/test/engine/pointer_binding_pointer_test.dart index c429c485ec256..df31986958220 100644 --- a/lib/web_ui/test/engine/pointer_binding_pointer_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_pointer_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:html' as html; +import 'dart:js_util' as js_util; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; @@ -10,51 +11,49 @@ import 'package:ui/ui.dart' as ui; import 'package:test/test.dart'; void main() { - group('Pointer Binding', () { - html.Element glassPane = domRenderer.glassPaneElement; + html.Element glassPane = domRenderer.glassPaneElement; - setUp(() { - // Touching domRenderer creates PointerBinding.instance. - domRenderer; + setUp(() { + // Touching domRenderer creates PointerBinding.instance. + domRenderer; - // Set a new detector to reset the state of the listeners. - PointerBinding.instance.debugOverrideDetector(TestPointerDetector()); + ui.window.onPointerDataPacket = null; + }); - ui.window.onPointerDataPacket = null; - }); + // ALL ADAPTERS - test('can receive pointer events on the glass pane', () { + [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { + test('${context.name} can receive pointer events on the glass pane', () { + PointerBinding.instance.debugOverrideDetector(context); ui.PointerDataPacket receivedPacket; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { receivedPacket = packet; }; - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 1, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); expect(receivedPacket, isNotNull); - expect(receivedPacket.data[0].device, equals(1)); + expect(receivedPacket.data[0].buttons, equals(1)); }); + }); - test('synthesizes a pointerup event on two pointerdowns in a row', () { + [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { + test('${context.name} synthesizes a pointerup event on two pointerdowns in a row', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); expect(packets, hasLength(2)); // An add will be synthesized. @@ -65,73 +64,19 @@ void main() { expect(packets[1].data[0].change, equals(ui.PointerChange.up)); expect(packets[1].data[1].change, equals(ui.PointerChange.down)); }); + }); - test('does not synthesize pointer up if from different device', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - })); - - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 2, - 'button': 0, - 'buttons': 1, - })); - - expect(packets, hasLength(2)); - // An add will be synthesized. - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].device, equals(1)); - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[0].data[1].device, equals(1)); - // An add will be synthesized. - expect(packets[1].data, hasLength(2)); - expect(packets[1].data[0].change, equals(ui.PointerChange.add)); - expect(packets[1].data[0].synthesized, equals(true)); - expect(packets[1].data[0].device, equals(2)); - expect(packets[1].data[1].change, equals(ui.PointerChange.down)); - expect(packets[1].data[1].device, equals(2)); - }); - - test('creates an add event if the first pointer activity is a hover', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'button': -1, - 'buttons': 0, - })); - - expect(packets, hasLength(1)); - expect(packets.single.data, hasLength(2)); - - expect(packets.single.data[0].change, equals(ui.PointerChange.add)); - expect(packets.single.data[0].synthesized, equals(true)); - expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); - }); - - test('does create an add event if got a pointerdown', () { + [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { + test('${context.name} does create an add event if got a pointerdown', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -139,68 +84,47 @@ void main() { expect(packets.single.data[0].change, equals(ui.PointerChange.add)); expect(packets.single.data[1].change, equals(ui.PointerChange.down)); }); + }); - test('does calculate delta and pointer identifier correctly', () { + [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { + test('${context.name} does calculate delta and pointer identifier correctly', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'button': -1, - 'buttons': 0, - 'clientX': 10.0, - 'clientY': 10.0, - })); + glassPane.dispatchEvent((context + ..clientX = 10.0 + ..clientY = 10.0 + ).moveEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'button': -1, - 'buttons': 0, - 'clientX': 20.0, - 'clientY': 20.0, - })); + glassPane.dispatchEvent((context + ..clientX = 20.0 + ..clientY = 20.0 + ).moveEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - 'clientX': 20.0, - 'clientY': 20.0, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'button': -1, - 'buttons': 1, - 'clientX': 40.0, - 'clientY': 30.0, - })); + glassPane.dispatchEvent((context + ..clientX = 40.0 + ..clientY = 30.0 + ).moveEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerId': 1, - 'button': 0, - 'buttons': 0, - 'clientX': 40.0, - 'clientY': 30.0, - })); + glassPane.dispatchEvent((context + ..down = false + ).upEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'button': -1, - 'buttons': 0, - 'clientX': 20.0, - 'clientY': 10.0, - })); + glassPane.dispatchEvent((context + ..clientX = 20.0 + ..clientY = 10.0 + ).moveEvent()); - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'button': 0, - 'buttons': 1, - 'clientX': 20.0, - 'clientY': 10.0, - })); + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); expect(packets, hasLength(7)); @@ -275,8 +199,32 @@ void main() { expect(packets[6].data[0].physicalDeltaX, equals(0.0)); expect(packets[6].data[0].physicalDeltaY, equals(0.0)); }); + }); + + // BUTTONED ADAPTERS - test('does synthesize add or hover or more for scroll', () { + [_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventContext context) { + test('${context.name} creates an add event if the first pointer activity is a hover', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(context.moveEvent()); + + expect(packets, hasLength(1)); + expect(packets.single.data, hasLength(2)); + + expect(packets.single.data[0].change, equals(ui.PointerChange.add)); + expect(packets.single.data[0].synthesized, equals(true)); + expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); + }); + }); + + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + test('${context.name} does synthesize add or hover or more for scroll', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); @@ -298,12 +246,12 @@ void main() { deltaY: 10, )); - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': -1, - 'button': 1, - 'clientX': 20.0, - 'clientY': 50.0, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 1 + ..clientX = 20.0 + ..clientY = 50.0 + ).downEvent()); glassPane.dispatchEvent(html.WheelEvent('wheel', button: 1, @@ -383,21 +331,22 @@ void main() { expect(packets[3].data[1].physicalDeltaX, equals(0.0)); expect(packets[3].data[1].physicalDeltaY, equals(0.0)); }); + }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { test('correctly converts buttons of down, move and up events', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; // Add and hover - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 0, - 'clientX': 10, - 'clientY': 11, - })); + + glassPane.dispatchEvent((context + ..clientX = 10 + ..clientY = 11 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -413,14 +362,12 @@ void main() { expect(packets[0].data[1].buttons, equals(0)); packets.clear(); - // Drag with primary button - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerType': 'mouse', - 'button': 0, - 'buttons': 1, - 'clientX': 10.0, - 'clientY': 11.0, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 1 + ..clientX = 10.0 + ..clientY = 11.0 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -430,13 +377,10 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 1, - 'clientX': 20.0, - 'clientY': 21.0, - })); + glassPane.dispatchEvent((context + ..clientX = 20.0 + ..clientY = 21.0 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -446,13 +390,10 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerType': 'mouse', - 'button': 0, - 'buttons': 0, - 'clientX': 20.0, - 'clientY': 21.0, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -463,13 +404,12 @@ void main() { packets.clear(); // Drag with secondary button - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 2, - 'clientX': 20.0, - 'clientY': 21.0, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 2 + ..clientX = 20.0 + ..clientY = 21.0 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -481,11 +421,13 @@ void main() { glassPane.dispatchEvent(html.PointerEvent('pointermove', { 'pointerType': 'mouse', - 'button': -1, - 'buttons': 2, - 'clientX': 30.0, - 'clientY': 31.0, })); + glassPane.dispatchEvent((context + ..button = -1 + ..buttons = 2 + ..clientX = 30.0 + ..clientY = 31.0 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -495,13 +437,10 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 0, - 'clientX': 30.0, - 'clientY': 31.0, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -512,13 +451,12 @@ void main() { packets.clear(); // Drag with middle button - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerType': 'mouse', - 'button': 1, - 'buttons': 4, - 'clientX': 30.0, - 'clientY': 31.0, - })); + glassPane.dispatchEvent((context + ..button = 1 + ..buttons = 4 + ..clientX = 30.0 + ..clientY = 31.0 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -528,13 +466,10 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 4, - 'clientX': 40.0, - 'clientY': 41.0, - })); + glassPane.dispatchEvent((context + ..clientX = 40.0 + ..clientY = 41.0 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -544,13 +479,10 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerType': 'mouse', - 'button': 1, - 'buttons': 0, - 'clientX': 40.0, - 'clientY': 41.0, - })); + glassPane.dispatchEvent((context + ..button = 1 + ..buttons = 0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -560,19 +492,21 @@ void main() { expect(packets[0].data[0].buttons, equals(0)); packets.clear(); }); + }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { test('correctly handles button changes during a down sequence', () { + PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; // Press LMB. - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerType': 'mouse', - 'button': 0, - 'buttons': 1, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 1 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -584,11 +518,10 @@ void main() { packets.clear(); // Press MMB. - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': 1, - 'buttons': 5, - })); + glassPane.dispatchEvent((context + ..button = 1 + ..buttons = 5 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -597,11 +530,10 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': 0, - 'buttons': 4, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 4 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -610,11 +542,10 @@ void main() { packets.clear(); // Release MMB. - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerType': 'mouse', - 'button': 1, - 'buttons': 0, - })); + glassPane.dispatchEvent((context + ..button = 1 + ..buttons = 0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -622,8 +553,11 @@ void main() { expect(packets[0].data[0].buttons, equals(0)); packets.clear(); }); + }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { test('synthesizes a pointerup event when pointermove comes before the up', () { + PointerBinding.instance.debugOverrideDetector(context); // This can happen when the user pops up the context menu by right // clicking, then dismisses it with a left click. @@ -632,14 +566,12 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerId': 1, - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 2, - 'clientX': 10, - 'clientY': 11, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 2 + ..clientX = 10 + ..clientY = 11 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -655,15 +587,12 @@ void main() { expect(packets[0].data[1].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 2, - 'clientX': 20.0, - 'clientY': 21.0, - })); - + glassPane.dispatchEvent((context + ..button = -1 + ..buttons = 2 + ..clientX = 20.0 + ..clientY = 21.0 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -673,15 +602,13 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerId': 1, - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 2, - 'clientX': 20.0, - 'clientY': 21.0, - })); + glassPane.dispatchEvent((context + ..button = -1 + ..buttons = 2 + ..clientX = 20.0 + ..clientY = 21.0 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -691,15 +618,13 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerId': 1, - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 0, - 'clientX': 20.0, - 'clientY': 21.0, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 0 + ..clientX = 20.0 + ..clientY = 21.0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -709,8 +634,11 @@ void main() { expect(packets[0].data[0].buttons, equals(0)); packets.clear(); }); + }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { test('correctly handles uncontinuous button changes during a down sequence', () { + PointerBinding.instance.debugOverrideDetector(context); // This can happen with the following gesture sequence: // // - Pops up the context menu by right clicking, but holds RMB; @@ -723,11 +651,10 @@ void main() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(html.PointerEvent('pointerdown', { - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 2, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 2 + ).downEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -741,11 +668,10 @@ void main() { // Press LMB. The event will have "button: -1" here, despite the change // in "buttons", probably because the "press" gesture was absorbed by // dismissing the context menu. - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': -1, - 'buttons': 3, - })); + glassPane.dispatchEvent((context + ..button = -1 + ..buttons = 3 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -754,11 +680,10 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - 'button': 0, - 'buttons': 2, - })); + glassPane.dispatchEvent((context + ..button = 0 + ..buttons = 2 + ).moveEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -767,11 +692,10 @@ void main() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(html.PointerEvent('pointerup', { - 'pointerType': 'mouse', - 'button': 2, - 'buttons': 0, - })); + glassPane.dispatchEvent((context + ..button = 2 + ..buttons = 0 + ).upEvent()); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -780,15 +704,196 @@ void main() { packets.clear(); }); }); + + // POINTER ADAPTER + + [_PointerEventContext()].forEach((_PointerEventContext context) { + test('${context.name} does not synthesize pointer up if from different device', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent((context + ..pointerType = 'touch' + ..pointerId = 1 + ..button = 0 + ..buttons = 1 + ).downEvent()); + + glassPane.dispatchEvent((context + ..pointerType = 'touch' + ..pointerId = 2 + ..button = 0 + ..buttons = 1 + ).downEvent()); + + expect(packets, hasLength(2)); + // An add will be synthesized. + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].device, equals(1)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].device, equals(1)); + // An add will be synthesized. + expect(packets[1].data, hasLength(2)); + expect(packets[1].data[0].change, equals(ui.PointerChange.add)); + expect(packets[1].data[0].synthesized, equals(true)); + expect(packets[1].data[0].device, equals(2)); + expect(packets[1].data[1].change, equals(ui.PointerChange.down)); + expect(packets[1].data[1].device, equals(2)); + }); + }); } -class TestPointerDetector extends PointerSupportDetector { +abstract class _BasicEventContext implements PointerSupportDetector { + String get name; + + double clientX = 0; + double clientY = 0; + bool get down; + set down(bool value); + + html.Event downEvent(); + html.Event moveEvent(); + html.Event upEvent(); +} + +abstract class _ButtonedEventContext extends _BasicEventContext { + int button = 0; + int buttons = 0; + + @override + bool get down => buttons != 0; + + @override + set down(bool value) { + if (value != down) { + button = 0; + buttons = value ? 1 : 0; + } + } + + int getButtonAndReset() { + int result = button; + button = -1; + return result; + } +} + +class _MouseEventContext extends _ButtonedEventContext implements PointerSupportDetector { + @override + String get name => 'MouseAdapter'; + @override - final bool hasPointerEvents = true; + bool get hasPointerEvents => false; @override - final bool hasTouchEvents = false; + bool get hasTouchEvents => false; @override - final bool hasMouseEvents = false; + bool get hasMouseEvents => true; + + html.Event downEvent() { + return _createMouseEvent( + 'mousedown', + buttons: buttons, + button: getButtonAndReset(), + clientX: clientX, + clientY: clientY, + ); + } + + html.Event moveEvent() { + return _createMouseEvent( + 'mousemove', + buttons: buttons, + button: getButtonAndReset(), + clientX: clientX, + clientY: clientY, + ); + } + + html.Event upEvent() { + return _createMouseEvent( + 'mouseup', + buttons: buttons, + button: getButtonAndReset(), + clientX: clientX, + clientY: clientY, + ); + } + + html.MouseEvent _createMouseEvent( + String type, { + int buttons, + int button, + double clientX, + double clientY, + }) { + final Function jsMouseEvent = js_util.getProperty(html.window, 'MouseEvent'); + final List eventArgs = [ + type, + { + 'buttons': buttons, + 'button': button, + 'clientX': clientX, + 'clientY': clientY, + } + ]; + return js_util.callConstructor(jsMouseEvent, js_util.jsify(eventArgs)); + } +} + + +class _PointerEventContext extends _ButtonedEventContext implements PointerSupportDetector { + @override + String get name => 'PointerAdapter'; + + @override + bool get hasPointerEvents => true; + + @override + bool get hasTouchEvents => false; + + @override + bool get hasMouseEvents => false; + + int pointerId = 0; + String pointerType = 'mouse'; + + html.Event downEvent() { + return html.PointerEvent('pointerdown', { + 'pointerId': pointerId, + 'button': getButtonAndReset(), + 'buttons': buttons, + 'clientX': clientX, + 'clientY': clientY, + 'pointerType': pointerType, + }); + } + + html.Event moveEvent() { + return html.PointerEvent('pointermove', { + 'pointerId': pointerId, + 'button': getButtonAndReset(), + 'buttons': buttons, + 'clientX': clientX, + 'clientY': clientY, + 'pointerType': pointerType, + }); + } + + html.Event upEvent() { + return html.PointerEvent('pointerup', { + 'pointerId': pointerId, + 'button': getButtonAndReset(), + 'buttons': buttons, + 'clientX': clientX, + 'clientY': clientY, + 'pointerType': pointerType, + }); + } } From f9fe5c7faa4f450a5ef4f76498dc20ba1c83de83 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 19:51:58 -0800 Subject: [PATCH 13/28] Test touch --- .../lib/src/engine/pointer_binding.dart | 5 +- .../lib/src/engine/pointer_converter.dart | 4 +- .../engine/pointer_binding_mouse_test.dart | 787 ------------------ ...er_test.dart => pointer_binding_test.dart} | 450 +++++++--- 4 files changed, 320 insertions(+), 926 deletions(-) delete mode 100644 lib/web_ui/test/engine/pointer_binding_mouse_test.dart rename lib/web_ui/test/engine/{pointer_binding_pointer_test.dart => pointer_binding_test.dart} (83%) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 6e4920503c8ab..c5879f05b21d6 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -513,8 +513,11 @@ class _TouchAdapter extends _BaseAdapter { ui.PointerChange change, html.TouchEvent event, ) { + final html.TouchList touches = event.changedTouches; final List data = List(); - for (html.Touch touch in event.changedTouches) { + final int len = touches.length; + for (int i = 0; i < len; i++) { + final html.Touch touch = touches[i]; _pointerDataConverter.convert( data, change: change, diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index c6b081558913e..fbedc00748c4f 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -333,10 +333,11 @@ class PointerDataConverter { final _PointerState state = _ensureStateForPointer( device, physicalX, physicalY); assert(!state.down); + state.startNewPointer(); if (!alreadyAdded) { // Synthesizes an add pointer data. result.add( - _synthesizePointerData( + _synthesizePointerData( timeStamp: timeStamp, change: ui.PointerChange.add, kind: kind, @@ -364,7 +365,6 @@ class PointerDataConverter { ); } assert(!_locationHasChanged(device, physicalX, physicalY)); - state.startNewPointer(); state.down = true; result.add( _generateCompletePointerData( diff --git a/lib/web_ui/test/engine/pointer_binding_mouse_test.dart b/lib/web_ui/test/engine/pointer_binding_mouse_test.dart deleted file mode 100644 index 38e83c388bd08..0000000000000 --- a/lib/web_ui/test/engine/pointer_binding_mouse_test.dart +++ /dev/null @@ -1,787 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:html' as html; -import 'dart:js_util' as js_util; - -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -import 'package:test/test.dart'; - -void main() { - group('Pointer Binding', () { - html.Element glassPane = domRenderer.glassPaneElement; - - setUp(() { - // Touching domRenderer creates PointerBinding.instance. - domRenderer; - - // Set a new detector to reset the state of the listeners. - PointerBinding.instance.debugOverrideDetector(TestPointerDetector()); - - ui.window.onPointerDataPacket = null; - }); - - test('can receive pointer events on the glass pane', () { - ui.PointerDataPacket receivedPacket; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - receivedPacket = packet; - }; - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - )); - - expect(receivedPacket, isNotNull); - expect(receivedPacket.data[0].device, equals(-1)); - }); - - test('synthesizes a pointerup event on two pointerdowns in a row', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - )); - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - )); - - expect(packets, hasLength(2)); - // An add will be synthesized. - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[1].data[0].change, equals(ui.PointerChange.up)); - expect(packets[1].data[1].change, equals(ui.PointerChange.down)); - }); - - test('creates an add event if the first pointer activity is a hover', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 0, - )); - - expect(packets, hasLength(1)); - expect(packets.single.data, hasLength(2)); - - expect(packets.single.data[0].change, equals(ui.PointerChange.add)); - expect(packets.single.data[0].synthesized, equals(true)); - expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); - }); - - test('does create an add event if got a pointerdown', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - )); - - expect(packets, hasLength(1)); - expect(packets.single.data, hasLength(2)); - - expect(packets.single.data[0].change, equals(ui.PointerChange.add)); - expect(packets.single.data[1].change, equals(ui.PointerChange.down)); - }); - - test('does calculate delta and pointer identifier correctly', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 0, - clientX: 10.0, - clientY: 10.0, - )); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 0, - clientX: 20.0, - clientY: 20.0, - )); - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - clientX: 20.0, - clientY: 20.0, - )); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 1, - clientX: 40.0, - clientY: 30.0, - )); - - glassPane.dispatchEvent(_upEvent( - button: 0, - buttons: 0, - clientX: 40.0, - clientY: 30.0, - )); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 0, - clientX: 20.0, - clientY: 10.0, - )); - - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - clientX: 20.0, - clientY: 10.0, - )); - - expect(packets, hasLength(7)); - - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].pointerIdentifier, equals(0)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10.0)); - expect(packets[0].data[0].physicalY, equals(10.0)); - expect(packets[0].data[0].physicalDeltaX, equals(0.0)); - expect(packets[0].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[0].data[1].pointerIdentifier, equals(0)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10.0)); - expect(packets[0].data[1].physicalY, equals(10.0)); - expect(packets[0].data[1].physicalDeltaX, equals(0.0)); - expect(packets[0].data[1].physicalDeltaY, equals(0.0)); - - expect(packets[1].data, hasLength(1)); - expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[0].pointerIdentifier, equals(0)); - expect(packets[1].data[0].synthesized, equals(false)); - expect(packets[1].data[0].physicalX, equals(20.0)); - expect(packets[1].data[0].physicalY, equals(20.0)); - expect(packets[1].data[0].physicalDeltaX, equals(10.0)); - expect(packets[1].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[2].data, hasLength(1)); - expect(packets[2].data[0].change, equals(ui.PointerChange.down)); - expect(packets[2].data[0].pointerIdentifier, equals(1)); - expect(packets[2].data[0].synthesized, equals(false)); - expect(packets[2].data[0].physicalX, equals(20.0)); - expect(packets[2].data[0].physicalY, equals(20.0)); - expect(packets[2].data[0].physicalDeltaX, equals(0.0)); - expect(packets[2].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[3].data, hasLength(1)); - expect(packets[3].data[0].change, equals(ui.PointerChange.move)); - expect(packets[3].data[0].pointerIdentifier, equals(1)); - expect(packets[3].data[0].synthesized, equals(false)); - expect(packets[3].data[0].physicalX, equals(40.0)); - expect(packets[3].data[0].physicalY, equals(30.0)); - expect(packets[3].data[0].physicalDeltaX, equals(20.0)); - expect(packets[3].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[4].data, hasLength(1)); - expect(packets[4].data[0].change, equals(ui.PointerChange.up)); - expect(packets[4].data[0].pointerIdentifier, equals(1)); - expect(packets[4].data[0].synthesized, equals(false)); - expect(packets[4].data[0].physicalX, equals(40.0)); - expect(packets[4].data[0].physicalY, equals(30.0)); - expect(packets[4].data[0].physicalDeltaX, equals(0.0)); - expect(packets[4].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[5].data, hasLength(1)); - expect(packets[5].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[5].data[0].pointerIdentifier, equals(1)); - expect(packets[5].data[0].synthesized, equals(false)); - expect(packets[5].data[0].physicalX, equals(20.0)); - expect(packets[5].data[0].physicalY, equals(10.0)); - expect(packets[5].data[0].physicalDeltaX, equals(-20.0)); - expect(packets[5].data[0].physicalDeltaY, equals(-20.0)); - - expect(packets[6].data, hasLength(1)); - expect(packets[6].data[0].change, equals(ui.PointerChange.down)); - expect(packets[6].data[0].pointerIdentifier, equals(2)); - expect(packets[6].data[0].synthesized, equals(false)); - expect(packets[6].data[0].physicalX, equals(20.0)); - expect(packets[6].data[0].physicalY, equals(10.0)); - expect(packets[6].data[0].physicalDeltaX, equals(0.0)); - expect(packets[6].data[0].physicalDeltaY, equals(0.0)); - }); - - test('does synthesize add or hover or more for scroll', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(html.WheelEvent('wheel', - button: 1, - clientX: 10, - clientY: 10, - deltaX: 10, - deltaY: 10, - )); - - glassPane.dispatchEvent(html.WheelEvent('wheel', - button: 1, - clientX: 20, - clientY: 50, - deltaX: 10, - deltaY: 10, - )); - - glassPane.dispatchEvent(_downEvent( - button: 1, - clientX: 20.0, - clientY: 50.0, - )); - - glassPane.dispatchEvent(html.WheelEvent('wheel', - button: 1, - clientX: 30, - clientY: 60, - deltaX: 10, - deltaY: 10, - )); - - expect(packets, hasLength(4)); - - // An add will be synthesized. - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].pointerIdentifier, equals(0)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10.0)); - expect(packets[0].data[0].physicalY, equals(10.0)); - expect(packets[0].data[0].physicalDeltaX, equals(0.0)); - expect(packets[0].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[0].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[0].data[1].pointerIdentifier, equals(0)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10.0)); - expect(packets[0].data[1].physicalY, equals(10.0)); - expect(packets[0].data[1].physicalDeltaX, equals(0.0)); - expect(packets[0].data[1].physicalDeltaY, equals(0.0)); - - // A hover will be synthesized. - expect(packets[1].data, hasLength(2)); - expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[0].pointerIdentifier, equals(0)); - expect(packets[1].data[0].synthesized, equals(true)); - expect(packets[1].data[0].physicalX, equals(20.0)); - expect(packets[1].data[0].physicalY, equals(50.0)); - expect(packets[1].data[0].physicalDeltaX, equals(10.0)); - expect(packets[1].data[0].physicalDeltaY, equals(40.0)); - - expect(packets[1].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[1].data[1].pointerIdentifier, equals(0)); - expect(packets[1].data[1].synthesized, equals(false)); - expect(packets[1].data[1].physicalX, equals(20.0)); - expect(packets[1].data[1].physicalY, equals(50.0)); - expect(packets[1].data[1].physicalDeltaX, equals(0.0)); - expect(packets[1].data[1].physicalDeltaY, equals(0.0)); - - // No synthetic pointer data for down event. - expect(packets[2].data, hasLength(1)); - expect(packets[2].data[0].change, equals(ui.PointerChange.down)); - expect(packets[2].data[0].signalKind, equals(ui.PointerSignalKind.none)); - expect(packets[2].data[0].pointerIdentifier, equals(1)); - expect(packets[2].data[0].synthesized, equals(false)); - expect(packets[2].data[0].physicalX, equals(20.0)); - expect(packets[2].data[0].physicalY, equals(50.0)); - expect(packets[2].data[0].physicalDeltaX, equals(0.0)); - expect(packets[2].data[0].physicalDeltaY, equals(0.0)); - - // A move will be synthesized instead of hover because the button is currently down. - expect(packets[3].data, hasLength(2)); - expect(packets[3].data[0].change, equals(ui.PointerChange.move)); - expect(packets[3].data[0].pointerIdentifier, equals(1)); - expect(packets[3].data[0].synthesized, equals(true)); - expect(packets[3].data[0].physicalX, equals(30.0)); - expect(packets[3].data[0].physicalY, equals(60.0)); - expect(packets[3].data[0].physicalDeltaX, equals(10.0)); - expect(packets[3].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[3].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[3].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[3].data[1].pointerIdentifier, equals(1)); - expect(packets[3].data[1].synthesized, equals(false)); - expect(packets[3].data[1].physicalX, equals(30.0)); - expect(packets[3].data[1].physicalY, equals(60.0)); - expect(packets[3].data[1].physicalDeltaX, equals(0.0)); - expect(packets[3].data[1].physicalDeltaY, equals(0.0)); - }); - - test('correctly converts buttons of down, move and up events', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - // Add and hover - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 0, - clientX: 10, - clientY: 11, - )); - - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10)); - expect(packets[0].data[0].physicalY, equals(11)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10)); - expect(packets[0].data[1].physicalY, equals(11)); - expect(packets[0].data[1].buttons, equals(0)); - packets.clear(); - - // Drag with primary button - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - clientX: 10.0, - clientY: 11.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.down)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(10)); - expect(packets[0].data[0].physicalY, equals(11)); - expect(packets[0].data[0].buttons, equals(1)); - packets.clear(); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 1, - clientX: 20.0, - clientY: 21.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(1)); - packets.clear(); - - glassPane.dispatchEvent(_upEvent( - button: 0, - buttons: 0, - clientX: 20.0, - clientY: 21.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - - // Drag with secondary button - glassPane.dispatchEvent(_downEvent( - button: 2, - buttons: 2, - clientX: 20.0, - clientY: 21.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.down)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(2)); - packets.clear(); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 2, - clientX: 30.0, - clientY: 31.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(30)); - expect(packets[0].data[0].physicalY, equals(31)); - expect(packets[0].data[0].buttons, equals(2)); - packets.clear(); - - glassPane.dispatchEvent(_upEvent( - button: 2, - buttons: 0, - clientX: 30.0, - clientY: 31.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(30)); - expect(packets[0].data[0].physicalY, equals(31)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - - // Drag with middle button - glassPane.dispatchEvent(_downEvent( - button: 1, - buttons: 4, - clientX: 30.0, - clientY: 31.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.down)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(30)); - expect(packets[0].data[0].physicalY, equals(31)); - expect(packets[0].data[0].buttons, equals(4)); - packets.clear(); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 4, - clientX: 40.0, - clientY: 41.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(40)); - expect(packets[0].data[0].physicalY, equals(41)); - expect(packets[0].data[0].buttons, equals(4)); - packets.clear(); - - glassPane.dispatchEvent(_upEvent( - button: 1, - buttons: 0, - clientX: 40.0, - clientY: 41.0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(40)); - expect(packets[0].data[0].physicalY, equals(41)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - }); - - test('correctly handles button changes during a down sequence', () { - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - // Press LMB. - glassPane.dispatchEvent(_downEvent( - button: 0, - buttons: 1, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].buttons, equals(1)); - packets.clear(); - - // Press MMB. - glassPane.dispatchEvent(_moveEvent( - button: 1, - buttons: 5, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(5)); - packets.clear(); - - // Release LMB. - glassPane.dispatchEvent(_moveEvent( - button: 0, - buttons: 4, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(4)); - packets.clear(); - - // Release MMB. - glassPane.dispatchEvent(_upEvent( - button: 1, - buttons: 0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - }); - - test('synthesizes a pointerup event when pointermove comes before the up', () { - // This can happen when the user pops up the context menu by right - // clicking, then dismisses it with a left click. - - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(_downEvent( - button: 2, - buttons: 2, - clientX: 10, - clientY: 11, - )); - - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10)); - expect(packets[0].data[0].physicalY, equals(11)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10)); - expect(packets[0].data[1].physicalY, equals(11)); - expect(packets[0].data[1].buttons, equals(2)); - packets.clear(); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 2, - clientX: 20.0, - clientY: 21.0, - )); - - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(2)); - packets.clear(); - - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 2, - clientX: 20.0, - clientY: 21.0, - )); - - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(2)); - packets.clear(); - - glassPane.dispatchEvent(_upEvent( - button: 2, - buttons: 0, - clientX: 20.0, - clientY: 21.0, - )); - - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].physicalX, equals(20)); - expect(packets[0].data[0].physicalY, equals(21)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - }); - - test('correctly handles uncontinuous button changes during a down sequence', () { - // This can happen with the following gesture sequence: - // - // - Pops up the context menu by right clicking, but holds RMB; - // - Clicks LMB; - // - Releases RMB. - - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(_downEvent( - button: 2, - buttons: 2, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].buttons, equals(2)); - packets.clear(); - - // Press LMB. The event will have "button: -1" here, despite the change - // in "buttons", probably because the "press" gesture was absorbed by - // dismissing the context menu. - glassPane.dispatchEvent(_moveEvent( - button: -1, - buttons: 3, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(3)); - packets.clear(); - - // Release LMB. - glassPane.dispatchEvent(_moveEvent( - button: 0, - buttons: 2, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(2)); - packets.clear(); - - // Release RMB. - glassPane.dispatchEvent(_upEvent( - button: 2, - buttons: 0, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.up)); - expect(packets[0].data[0].synthesized, equals(false)); - expect(packets[0].data[0].buttons, equals(0)); - packets.clear(); - }); - }); -} - -class TestPointerDetector extends PointerSupportDetector { - @override - final bool hasPointerEvents = false; - - @override - final bool hasTouchEvents = false; - - @override - final bool hasMouseEvents = true; -} - -html.MouseEvent _createMouseEvent( - String type, { - int buttons, - int button, - double clientX, - double clientY, -}) { - final Function jsMouseEvent = js_util.getProperty(html.window, 'MouseEvent'); - final List eventArgs = [ - type, - { - 'buttons': buttons, - 'button': button, - 'clientX': clientX, - 'clientY': clientY, - } - ]; - return js_util.callConstructor(jsMouseEvent, js_util.jsify(eventArgs)); -} - -html.MouseEvent _downEvent({ - int buttons, - int button, - double clientX = 0, - double clientY = 0, -}) { - return _createMouseEvent( - 'mousedown', - buttons: buttons, - button: button, - clientX: clientX, - clientY: clientY, - ); -} - -html.MouseEvent _moveEvent({ - int buttons, - int button, - double clientX = 0, - double clientY = 0, -}) { - return _createMouseEvent( - 'mousemove', - buttons: buttons, - button: button, - clientX: clientX, - clientY: clientY, - ); -} - -html.MouseEvent _upEvent({ - int buttons = 0, - int button, - double clientX = 0, - double clientY = 0, -}) { - return _createMouseEvent( - 'mouseup', - buttons: buttons, - button: button, - clientX: clientX, - clientY: clientY, - ); -} diff --git a/lib/web_ui/test/engine/pointer_binding_pointer_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart similarity index 83% rename from lib/web_ui/test/engine/pointer_binding_pointer_test.dart rename to lib/web_ui/test/engine/pointer_binding_test.dart index df31986958220..20c918496143f 100644 --- a/lib/web_ui/test/engine/pointer_binding_pointer_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -22,7 +22,7 @@ void main() { // ALL ADAPTERS - [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { + [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()].forEach((_BasicEventContext context) { test('${context.name} can receive pointer events on the glass pane', () { PointerBinding.instance.debugOverrideDetector(context); ui.PointerDataPacket receivedPacket; @@ -39,8 +39,8 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { - test('${context.name} synthesizes a pointerup event on two pointerdowns in a row', () { + [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()].forEach((_BasicEventContext context) { + test('${context.name} does create an add event if got a pointerdown', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -51,174 +51,59 @@ void main() { ..down = true ).downEvent()); - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + expect(packets, hasLength(1)); + expect(packets.single.data, hasLength(2)); - expect(packets, hasLength(2)); - // An add will be synthesized. - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[1].data[0].change, equals(ui.PointerChange.up)); - expect(packets[1].data[1].change, equals(ui.PointerChange.down)); + expect(packets.single.data[0].change, equals(ui.PointerChange.add)); + expect(packets.single.data[1].change, equals(ui.PointerChange.down)); }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { - test('${context.name} does create an add event if got a pointerdown', () { + // BUTTONED ADAPTERS + + [_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventContext context) { + test('${context.name} creates an add event if the first pointer activity is a hover', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.moveEvent()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); expect(packets.single.data[0].change, equals(ui.PointerChange.add)); - expect(packets.single.data[1].change, equals(ui.PointerChange.down)); + expect(packets.single.data[0].synthesized, equals(true)); + expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_BasicEventContext context) { - test('${context.name} does calculate delta and pointer identifier correctly', () { + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + test('${context.name} synthesizes a pointerup event on two pointerdowns in a row', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent((context - ..clientX = 10.0 - ..clientY = 10.0 - ).moveEvent()); - - glassPane.dispatchEvent((context - ..clientX = 20.0 - ..clientY = 20.0 - ).moveEvent()); - glassPane.dispatchEvent((context ..down = true ).downEvent()); - glassPane.dispatchEvent((context - ..clientX = 40.0 - ..clientY = 30.0 - ).moveEvent()); - - glassPane.dispatchEvent((context - ..down = false - ).upEvent()); - - glassPane.dispatchEvent((context - ..clientX = 20.0 - ..clientY = 10.0 - ).moveEvent()); - glassPane.dispatchEvent((context ..down = true ).downEvent()); - expect(packets, hasLength(7)); - + expect(packets, hasLength(2)); + // An add will be synthesized. expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].pointerIdentifier, equals(0)); expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10.0)); - expect(packets[0].data[0].physicalY, equals(10.0)); - expect(packets[0].data[0].physicalDeltaX, equals(0.0)); - expect(packets[0].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[0].data[1].pointerIdentifier, equals(0)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10.0)); - expect(packets[0].data[1].physicalY, equals(10.0)); - expect(packets[0].data[1].physicalDeltaX, equals(0.0)); - expect(packets[0].data[1].physicalDeltaY, equals(0.0)); - - expect(packets[1].data, hasLength(1)); - expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[0].pointerIdentifier, equals(0)); - expect(packets[1].data[0].synthesized, equals(false)); - expect(packets[1].data[0].physicalX, equals(20.0)); - expect(packets[1].data[0].physicalY, equals(20.0)); - expect(packets[1].data[0].physicalDeltaX, equals(10.0)); - expect(packets[1].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[2].data, hasLength(1)); - expect(packets[2].data[0].change, equals(ui.PointerChange.down)); - expect(packets[2].data[0].pointerIdentifier, equals(1)); - expect(packets[2].data[0].synthesized, equals(false)); - expect(packets[2].data[0].physicalX, equals(20.0)); - expect(packets[2].data[0].physicalY, equals(20.0)); - expect(packets[2].data[0].physicalDeltaX, equals(0.0)); - expect(packets[2].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[3].data, hasLength(1)); - expect(packets[3].data[0].change, equals(ui.PointerChange.move)); - expect(packets[3].data[0].pointerIdentifier, equals(1)); - expect(packets[3].data[0].synthesized, equals(false)); - expect(packets[3].data[0].physicalX, equals(40.0)); - expect(packets[3].data[0].physicalY, equals(30.0)); - expect(packets[3].data[0].physicalDeltaX, equals(20.0)); - expect(packets[3].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[4].data, hasLength(1)); - expect(packets[4].data[0].change, equals(ui.PointerChange.up)); - expect(packets[4].data[0].pointerIdentifier, equals(1)); - expect(packets[4].data[0].synthesized, equals(false)); - expect(packets[4].data[0].physicalX, equals(40.0)); - expect(packets[4].data[0].physicalY, equals(30.0)); - expect(packets[4].data[0].physicalDeltaX, equals(0.0)); - expect(packets[4].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[5].data, hasLength(1)); - expect(packets[5].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[5].data[0].pointerIdentifier, equals(1)); - expect(packets[5].data[0].synthesized, equals(false)); - expect(packets[5].data[0].physicalX, equals(20.0)); - expect(packets[5].data[0].physicalY, equals(10.0)); - expect(packets[5].data[0].physicalDeltaX, equals(-20.0)); - expect(packets[5].data[0].physicalDeltaY, equals(-20.0)); - - expect(packets[6].data, hasLength(1)); - expect(packets[6].data[0].change, equals(ui.PointerChange.down)); - expect(packets[6].data[0].pointerIdentifier, equals(2)); - expect(packets[6].data[0].synthesized, equals(false)); - expect(packets[6].data[0].physicalX, equals(20.0)); - expect(packets[6].data[0].physicalY, equals(10.0)); - expect(packets[6].data[0].physicalDeltaX, equals(0.0)); - expect(packets[6].data[0].physicalDeltaY, equals(0.0)); - }); - }); - - // BUTTONED ADAPTERS - - [_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventContext context) { - test('${context.name} creates an add event if the first pointer activity is a hover', () { - PointerBinding.instance.debugOverrideDetector(context); - List packets = []; - ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { - packets.add(packet); - }; - - glassPane.dispatchEvent(context.moveEvent()); - - expect(packets, hasLength(1)); - expect(packets.single.data, hasLength(2)); - - expect(packets.single.data[0].change, equals(ui.PointerChange.add)); - expect(packets.single.data[0].synthesized, equals(true)); - expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[1].data[0].change, equals(ui.PointerChange.up)); + expect(packets[1].data[1].change, equals(ui.PointerChange.down)); }); }); @@ -333,6 +218,122 @@ void main() { }); }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + // Touch is in another test since this test involves hovering + test('${context.name} does calculate delta and pointer identifier correctly', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent((context + ..clientX = 10.0 + ..clientY = 10.0 + ).moveEvent()); + + glassPane.dispatchEvent((context + ..clientX = 20.0 + ..clientY = 20.0 + ).moveEvent()); + + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); + + glassPane.dispatchEvent((context + ..clientX = 40.0 + ..clientY = 30.0 + ).moveEvent()); + + glassPane.dispatchEvent((context + ..down = false + ).upEvent()); + + glassPane.dispatchEvent((context + ..clientX = 20.0 + ..clientY = 10.0 + ).moveEvent()); + + glassPane.dispatchEvent((context + ..down = true + ).downEvent()); + + expect(packets, hasLength(7)); + + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(0)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].pointerIdentifier, equals(0)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10.0)); + expect(packets[0].data[1].physicalY, equals(10.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + + expect(packets[1].data, hasLength(1)); + expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[1].data[0].pointerIdentifier, equals(0)); + expect(packets[1].data[0].synthesized, equals(false)); + expect(packets[1].data[0].physicalX, equals(20.0)); + expect(packets[1].data[0].physicalY, equals(20.0)); + expect(packets[1].data[0].physicalDeltaX, equals(10.0)); + expect(packets[1].data[0].physicalDeltaY, equals(10.0)); + + expect(packets[2].data, hasLength(1)); + expect(packets[2].data[0].change, equals(ui.PointerChange.down)); + expect(packets[2].data[0].pointerIdentifier, equals(1)); + expect(packets[2].data[0].synthesized, equals(false)); + expect(packets[2].data[0].physicalX, equals(20.0)); + expect(packets[2].data[0].physicalY, equals(20.0)); + expect(packets[2].data[0].physicalDeltaX, equals(0.0)); + expect(packets[2].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[3].data, hasLength(1)); + expect(packets[3].data[0].change, equals(ui.PointerChange.move)); + expect(packets[3].data[0].pointerIdentifier, equals(1)); + expect(packets[3].data[0].synthesized, equals(false)); + expect(packets[3].data[0].physicalX, equals(40.0)); + expect(packets[3].data[0].physicalY, equals(30.0)); + expect(packets[3].data[0].physicalDeltaX, equals(20.0)); + expect(packets[3].data[0].physicalDeltaY, equals(10.0)); + + expect(packets[4].data, hasLength(1)); + expect(packets[4].data[0].change, equals(ui.PointerChange.up)); + expect(packets[4].data[0].pointerIdentifier, equals(1)); + expect(packets[4].data[0].synthesized, equals(false)); + expect(packets[4].data[0].physicalX, equals(40.0)); + expect(packets[4].data[0].physicalY, equals(30.0)); + expect(packets[4].data[0].physicalDeltaX, equals(0.0)); + expect(packets[4].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[5].data, hasLength(1)); + expect(packets[5].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[5].data[0].pointerIdentifier, equals(1)); + expect(packets[5].data[0].synthesized, equals(false)); + expect(packets[5].data[0].physicalX, equals(20.0)); + expect(packets[5].data[0].physicalY, equals(10.0)); + expect(packets[5].data[0].physicalDeltaX, equals(-20.0)); + expect(packets[5].data[0].physicalDeltaY, equals(-20.0)); + + expect(packets[6].data, hasLength(1)); + expect(packets[6].data[0].change, equals(ui.PointerChange.down)); + expect(packets[6].data[0].pointerIdentifier, equals(2)); + expect(packets[6].data[0].synthesized, equals(false)); + expect(packets[6].data[0].physicalX, equals(20.0)); + expect(packets[6].data[0].physicalY, equals(10.0)); + expect(packets[6].data[0].physicalDeltaX, equals(0.0)); + expect(packets[6].data[0].physicalDeltaY, equals(0.0)); + }); + }); + [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { test('correctly converts buttons of down, move and up events', () { PointerBinding.instance.debugOverrideDetector(context); @@ -746,6 +747,98 @@ void main() { expect(packets[1].data[1].device, equals(2)); }); }); + + // TOUCH ADAPTER + + [_TouchEventContext()].forEach((_TouchEventContext context) { + // Mouse and Pointer are in another test since these tests can involve hovering + test('${context.name} does calculate delta and pointer identifier correctly', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent((context + ..identifier = 1 + ..down = true + ..clientX = 20.0 + ..clientY = 20.0 + ).downEvent()); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(20.0)); + expect(packets[0].data[0].physicalY, equals(20.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].pointerIdentifier, equals(1)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(20.0)); + expect(packets[0].data[1].physicalY, equals(20.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + packets.clear(); + + glassPane.dispatchEvent((context + ..clientX = 40.0 + ..clientY = 30.0 + ).moveEvent()); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40.0)); + expect(packets[0].data[0].physicalY, equals(30.0)); + expect(packets[0].data[0].physicalDeltaX, equals(20.0)); + expect(packets[0].data[0].physicalDeltaY, equals(10.0)); + packets.clear(); + + glassPane.dispatchEvent((context + ..down = false + ).upEvent()); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40.0)); + expect(packets[0].data[0].physicalY, equals(30.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + packets.clear(); + + glassPane.dispatchEvent((context + ..identifier = 2 + ..clientX = 20.0 + ..clientY = 10.0 + ..down = true + ).downEvent()); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(2)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(20.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].pointerIdentifier, equals(2)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(20.0)); + expect(packets[0].data[1].physicalY, equals(10.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + packets.clear(); + }); + }); } abstract class _BasicEventContext implements PointerSupportDetector { @@ -761,6 +854,91 @@ abstract class _BasicEventContext implements PointerSupportDetector { html.Event upEvent(); } +class _TouchEventContext extends _BasicEventContext implements PointerSupportDetector { + _TouchEventContext() { + _target = html.document.createElement('div'); + } + + @override + String get name => 'TouchAdapter'; + + @override + bool get hasPointerEvents => false; + + @override + bool get hasTouchEvents => true; + + @override + bool get hasMouseEvents => false; + + @override + bool get down => _down; + set down(bool value) { + _down = value; + } + bool _down; + + html.EventTarget _target; + + int identifier = 1; + + html.Touch _createTouch({ + int identifier, + double clientX, + double clientY, + }) { + return html.Touch({ + 'identifier': identifier, + 'clientX': clientX, + 'clientY': clientY, + 'target': _target, + }); + } + + html.TouchEvent _createTouchEvent(String eventType, { + int identifier, + double clientX, + double clientY, + }) { + return html.TouchEvent(eventType, { + 'changedTouches': [ + _createTouch( + identifier: identifier, + clientX: clientX, + clientY: clientY, + ) + ], + }); + } + + html.Event downEvent() { + return _createTouchEvent( + 'touchstart', + identifier: identifier, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event moveEvent() { + return _createTouchEvent( + 'touchmove', + identifier: identifier, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event upEvent() { + return _createTouchEvent( + 'touchend', + identifier: identifier, + clientX: clientX, + clientY: clientY, + ); + } +} + abstract class _ButtonedEventContext extends _BasicEventContext { int button = 0; int buttons = 0; From c83ad2e976f314a1bd95c2870c6413f76148c40b Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 13 Dec 2019 19:53:05 -0800 Subject: [PATCH 14/28] Iterator --- lib/web_ui/lib/src/engine/pointer_binding.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index c5879f05b21d6..6e4920503c8ab 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -513,11 +513,8 @@ class _TouchAdapter extends _BaseAdapter { ui.PointerChange change, html.TouchEvent event, ) { - final html.TouchList touches = event.changedTouches; final List data = List(); - final int len = touches.length; - for (int i = 0; i < len; i++) { - final html.Touch touch = touches[i]; + for (html.Touch touch in event.changedTouches) { _pointerDataConverter.convert( data, change: change, From c3a0083a2cf986a19ccb34dcaf6b5f26026d5e3b Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 16 Dec 2019 17:11:31 -0800 Subject: [PATCH 15/28] Change API --- .../test/engine/pointer_binding_test.dart | 532 ++++++++++-------- 1 file changed, 300 insertions(+), 232 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 20c918496143f..0d8a7ad1fe1d9 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -30,9 +30,7 @@ void main() { receivedPacket = packet; }; - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down()); expect(receivedPacket, isNotNull); expect(receivedPacket.data[0].buttons, equals(1)); @@ -47,9 +45,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -69,7 +65,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.moveEvent()); + glassPane.dispatchEvent(context.hoverEvent()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -88,13 +84,9 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down()); - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down()); expect(packets, hasLength(2)); // An add will be synthesized. @@ -131,12 +123,12 @@ void main() { deltaY: 10, )); - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 1 - ..clientX = 20.0 - ..clientY = 50.0 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 0, + buttons: 1, + clientX: 20.0, + clientY: 50.0, + )); glassPane.dispatchEvent(html.WheelEvent('wheel', button: 1, @@ -227,37 +219,40 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..clientX = 10.0 - ..clientY = 10.0 - ).moveEvent()); + glassPane.dispatchEvent(context.hoverEvent( + clientX: 10.0, + clientY: 10.0, + )); - glassPane.dispatchEvent((context - ..clientX = 20.0 - ..clientY = 20.0 - ).moveEvent()); + glassPane.dispatchEvent(context.hoverEvent( + clientX: 20.0, + clientY: 20.0, + )); - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down( + clientX: 20.0, + clientY: 20.0, + )); - glassPane.dispatchEvent((context - ..clientX = 40.0 - ..clientY = 30.0 - ).moveEvent()); + glassPane.dispatchEvent(context.move( + clientX: 40.0, + clientY: 30.0, + )); - glassPane.dispatchEvent((context - ..down = false - ).upEvent()); + glassPane.dispatchEvent(context.up( + clientX: 40.0, + clientY: 30.0, + )); - glassPane.dispatchEvent((context - ..clientX = 20.0 - ..clientY = 10.0 - ).moveEvent()); + glassPane.dispatchEvent(context.hoverEvent( + clientX: 20.0, + clientY: 10.0, + )); - glassPane.dispatchEvent((context - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.down( + clientX: 20.0, + clientY: 10.0, + )); expect(packets, hasLength(7)); @@ -344,10 +339,10 @@ void main() { // Add and hover - glassPane.dispatchEvent((context - ..clientX = 10 - ..clientY = 11 - ).moveEvent()); + glassPane.dispatchEvent(context.hoverEvent( + clientX: 10, + clientY: 11, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -363,12 +358,12 @@ void main() { expect(packets[0].data[1].buttons, equals(0)); packets.clear(); - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 1 - ..clientX = 10.0 - ..clientY = 11.0 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 0, + buttons: 1, + clientX: 10.0, + clientY: 11.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -378,10 +373,12 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent((context - ..clientX = 20.0 - ..clientY = 21.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 1, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -391,10 +388,11 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 0, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -405,12 +403,12 @@ void main() { packets.clear(); // Drag with secondary button - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 2 - ..clientX = 20.0 - ..clientY = 21.0 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 2, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -420,15 +418,12 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(html.PointerEvent('pointermove', { - 'pointerType': 'mouse', - })); - glassPane.dispatchEvent((context - ..button = -1 - ..buttons = 2 - ..clientX = 30.0 - ..clientY = 31.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 2, + clientX: 30.0, + clientY: 31.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -438,10 +433,11 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 2, + clientX: 30.0, + clientY: 31.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -452,12 +448,12 @@ void main() { packets.clear(); // Drag with middle button - glassPane.dispatchEvent((context - ..button = 1 - ..buttons = 4 - ..clientX = 30.0 - ..clientY = 31.0 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 1, + buttons: 4, + clientX: 30.0, + clientY: 31.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.down)); @@ -467,10 +463,12 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent((context - ..clientX = 40.0 - ..clientY = 41.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 4, + clientX: 40.0, + clientY: 41.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -480,10 +478,11 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent((context - ..button = 1 - ..buttons = 0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 1, + clientX: 40.0, + clientY: 41.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -504,10 +503,10 @@ void main() { }; // Press LMB. - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 1 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 0, + buttons: 1, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -519,10 +518,10 @@ void main() { packets.clear(); // Press MMB. - glassPane.dispatchEvent((context - ..button = 1 - ..buttons = 5 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: 1, + buttons: 5, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -531,10 +530,10 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 4 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: 0, + buttons: 4, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -543,10 +542,9 @@ void main() { packets.clear(); // Release MMB. - glassPane.dispatchEvent((context - ..button = 1 - ..buttons = 0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 1, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -567,12 +565,12 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 2 - ..clientX = 10 - ..clientY = 11 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 2, + buttons: 2, + clientX: 10, + clientY: 11, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -588,12 +586,12 @@ void main() { expect(packets[0].data[1].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent((context - ..button = -1 - ..buttons = 2 - ..clientX = 20.0 - ..clientY = 21.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -604,12 +602,12 @@ void main() { packets.clear(); - glassPane.dispatchEvent((context - ..button = -1 - ..buttons = 2 - ..clientX = 20.0 - ..clientY = 21.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 2, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -620,12 +618,11 @@ void main() { packets.clear(); - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 0 - ..clientX = 20.0 - ..clientY = 21.0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 2, + clientX: 20.0, + clientY: 21.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -652,10 +649,10 @@ void main() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 2 - ).downEvent()); + glassPane.dispatchEvent(context.downWithButton( + button: 2, + buttons: 2, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -669,10 +666,10 @@ void main() { // Press LMB. The event will have "button: -1" here, despite the change // in "buttons", probably because the "press" gesture was absorbed by // dismissing the context menu. - glassPane.dispatchEvent((context - ..button = -1 - ..buttons = 3 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: -1, + buttons: 3, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -681,10 +678,10 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent((context - ..button = 0 - ..buttons = 2 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithButton( + button: 0, + buttons: 2, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -693,10 +690,9 @@ void main() { packets.clear(); // Release RMB. - glassPane.dispatchEvent((context - ..button = 2 - ..buttons = 0 - ).upEvent()); + glassPane.dispatchEvent(context.upWithButton( + button: 2, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -718,17 +714,19 @@ void main() { glassPane.dispatchEvent((context ..pointerType = 'touch' - ..pointerId = 1 - ..button = 0 - ..buttons = 1 - ).downEvent()); + ).downWithButtonPointer( + pointer: 1, + button: 0, + buttons: 1, + )); glassPane.dispatchEvent((context ..pointerType = 'touch' - ..pointerId = 2 - ..button = 0 - ..buttons = 1 - ).downEvent()); + ).downWithButtonPointer( + pointer: 2, + button: 0, + buttons: 1, + )); expect(packets, hasLength(2)); // An add will be synthesized. @@ -759,12 +757,11 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..identifier = 1 - ..down = true - ..clientX = 20.0 - ..clientY = 20.0 - ).downEvent()); + glassPane.dispatchEvent(context.downWithPointer( + pointer: 1, + clientX: 20.0, + clientY: 20.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -784,10 +781,11 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent((context - ..clientX = 40.0 - ..clientY = 30.0 - ).moveEvent()); + glassPane.dispatchEvent(context.moveWithPointer( + pointer: 1, + clientX: 40.0, + clientY: 30.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -799,9 +797,11 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(10.0)); packets.clear(); - glassPane.dispatchEvent((context - ..down = false - ).upEvent()); + glassPane.dispatchEvent(context.upWithPointer( + pointer: 1, + clientX: 40.0, + clientY: 30.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -813,12 +813,11 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent((context - ..identifier = 2 - ..clientX = 20.0 - ..clientY = 10.0 - ..down = true - ).downEvent()); + glassPane.dispatchEvent(context.downWithPointer( + pointer: 2, + clientX: 20.0, + clientY: 10.0, + )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -844,14 +843,9 @@ void main() { abstract class _BasicEventContext implements PointerSupportDetector { String get name; - double clientX = 0; - double clientY = 0; - bool get down; - set down(bool value); - - html.Event downEvent(); - html.Event moveEvent(); - html.Event upEvent(); + html.Event down({double clientX, double clientY}); + html.Event move({double clientX, double clientY}); + html.Event up({double clientX, double clientY}); } class _TouchEventContext extends _BasicEventContext implements PointerSupportDetector { @@ -871,17 +865,8 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet @override bool get hasMouseEvents => false; - @override - bool get down => _down; - set down(bool value) { - _down = value; - } - bool _down; - html.EventTarget _target; - int identifier = 1; - html.Touch _createTouch({ int identifier, double clientX, @@ -911,28 +896,55 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet }); } - html.Event downEvent() { + @override + html.Event down({double clientX, double clientY}) { + return downWithPointer( + pointer: 1, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event downWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchstart', - identifier: identifier, + identifier: pointer, clientX: clientX, clientY: clientY, ); } - html.Event moveEvent() { + @override + html.Event move({double clientX, double clientY}) { + return moveWithPointer( + pointer: 1, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event moveWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchmove', - identifier: identifier, + identifier: pointer, + clientX: clientX, + clientY: clientY, + ); + } + + @override + html.Event up({double clientX, double clientY}) { + return upWithPointer( + pointer: 1, clientX: clientX, clientY: clientY, ); } - html.Event upEvent() { + html.Event upWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchend', - identifier: identifier, + identifier: pointer, clientX: clientX, clientY: clientY, ); @@ -940,24 +952,47 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet } abstract class _ButtonedEventContext extends _BasicEventContext { - int button = 0; - int buttons = 0; + html.Event downWithButton({double clientX, double clientY, int button, int buttons}); + html.Event moveWithButton({double clientX, double clientY, int button, int buttons}); + html.Event upWithButton({double clientX, double clientY, int button}); + + html.Event hoverEvent({double clientX, double clientY}) { + return moveWithButton( + buttons: 0, + button: -1, + clientX: clientX, + clientY: clientY, + ); + } @override - bool get down => buttons != 0; + html.Event down({double clientX, double clientY}) { + return downWithButton( + buttons: 1, + button: 0, + clientX: clientX, + clientY: clientY, + ); + } + @override - set down(bool value) { - if (value != down) { - button = 0; - buttons = value ? 1 : 0; - } + html.Event move({double clientX, double clientY}) { + return moveWithButton( + buttons: 1, + button: -1, + clientX: clientX, + clientY: clientY, + ); } - int getButtonAndReset() { - int result = button; - button = -1; - return result; + @override + html.Event up({double clientX, double clientY}) { + return upWithButton( + button: 1, + clientX: clientX, + clientY: clientY, + ); } } @@ -974,31 +1009,34 @@ class _MouseEventContext extends _ButtonedEventContext implements PointerSupport @override bool get hasMouseEvents => true; - html.Event downEvent() { + @override + html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { return _createMouseEvent( 'mousedown', buttons: buttons, - button: getButtonAndReset(), + button: button, clientX: clientX, clientY: clientY, ); } - html.Event moveEvent() { + @override + html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { return _createMouseEvent( 'mousemove', buttons: buttons, - button: getButtonAndReset(), + button: button, clientX: clientX, clientY: clientY, ); } - html.Event upEvent() { + @override + html.Event upWithButton({double clientX, double clientY, int button}) { return _createMouseEvent( 'mouseup', - buttons: buttons, - button: getButtonAndReset(), + buttons: 0, + button: button, clientX: clientX, clientY: clientY, ); @@ -1025,7 +1063,6 @@ class _MouseEventContext extends _ButtonedEventContext implements PointerSupport } } - class _PointerEventContext extends _ButtonedEventContext implements PointerSupportDetector { @override String get name => 'PointerAdapter'; @@ -1039,13 +1076,23 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo @override bool get hasMouseEvents => false; - int pointerId = 0; String pointerType = 'mouse'; - html.Event downEvent() { + @override + html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { + return downWithButtonPointer( + pointer: 1, + buttons: buttons, + button: button, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event downWithButtonPointer({double clientX, double clientY, int button, int buttons, int pointer}) { return html.PointerEvent('pointerdown', { - 'pointerId': pointerId, - 'button': getButtonAndReset(), + 'pointerId': pointer, + 'button': button, 'buttons': buttons, 'clientX': clientX, 'clientY': clientY, @@ -1053,10 +1100,21 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo }); } - html.Event moveEvent() { + @override + html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { + return moveWithButtonPointer( + pointer: 1, + buttons: buttons, + button: button, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event moveWithButtonPointer({double clientX, double clientY, int button, int buttons, int pointer}) { return html.PointerEvent('pointermove', { - 'pointerId': pointerId, - 'button': getButtonAndReset(), + 'pointerId': pointer, + 'button': button, 'buttons': buttons, 'clientX': clientX, 'clientY': clientY, @@ -1064,11 +1122,21 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo }); } - html.Event upEvent() { + @override + html.Event upWithButton({double clientX, double clientY, int button}) { + return upWithButtonPointer( + pointer: 1, + button: button, + clientX: clientX, + clientY: clientY, + ); + } + + html.Event upWithButtonPointer({double clientX, double clientY, int button, int pointer}) { return html.PointerEvent('pointerup', { - 'pointerId': pointerId, - 'button': getButtonAndReset(), - 'buttons': buttons, + 'pointerId': pointer, + 'button': button, + 'buttons': 0, 'clientX': clientX, 'clientY': clientY, 'pointerType': pointerType, From 7863442f0dd34905f953ced2ce14911788d06f78 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 16 Dec 2019 17:25:09 -0800 Subject: [PATCH 16/28] Mixins --- .../test/engine/pointer_binding_test.dart | 204 +++++++++++------- 1 file changed, 121 insertions(+), 83 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 0d8a7ad1fe1d9..beb812b7868ec 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -57,7 +57,7 @@ void main() { // BUTTONED ADAPTERS - [_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventMixin context) { test('${context.name} creates an add event if the first pointer activity is a hover', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; @@ -76,7 +76,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('${context.name} synthesizes a pointerup event on two pointerdowns in a row', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; @@ -99,7 +99,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('${context.name} does synthesize add or hover or more for scroll', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; @@ -210,7 +210,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { // Touch is in another test since this test involves hovering test('${context.name} does calculate delta and pointer identifier correctly', () { PointerBinding.instance.debugOverrideDetector(context); @@ -329,7 +329,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('correctly converts buttons of down, move and up events', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; @@ -494,7 +494,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('correctly handles button changes during a down sequence', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; @@ -554,7 +554,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('synthesizes a pointerup event when pointermove comes before the up', () { PointerBinding.instance.debugOverrideDetector(context); // This can happen when the user pops up the context menu by right @@ -634,7 +634,7 @@ void main() { }); }); - [_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventContext context) { + <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { test('correctly handles uncontinuous button changes during a down sequence', () { PointerBinding.instance.debugOverrideDetector(context); // This can happen with the following gesture sequence: @@ -848,7 +848,85 @@ abstract class _BasicEventContext implements PointerSupportDetector { html.Event up({double clientX, double clientY}); } -class _TouchEventContext extends _BasicEventContext implements PointerSupportDetector { +mixin _ButtonedEventMixin on _BasicEventContext { + html.Event downWithButton({double clientX, double clientY, int button, int buttons}); + html.Event moveWithButton({double clientX, double clientY, int button, int buttons}); + html.Event upWithButton({double clientX, double clientY, int button}); + + html.Event hoverEvent({double clientX, double clientY}) { + return moveWithButton( + buttons: 0, + button: -1, + clientX: clientX, + clientY: clientY, + ); + } + + @override + html.Event down({double clientX, double clientY}) { + return downWithButton( + buttons: 1, + button: 0, + clientX: clientX, + clientY: clientY, + ); + } + + + @override + html.Event move({double clientX, double clientY}) { + return moveWithButton( + buttons: 1, + button: -1, + clientX: clientX, + clientY: clientY, + ); + } + + @override + html.Event up({double clientX, double clientY}) { + return upWithButton( + button: 1, + clientX: clientX, + clientY: clientY, + ); + } +} + +mixin _MultiPointerEventMixin on _BasicEventContext { + html.Event downWithPointer({double clientX, double clientY, int pointer}); + html.Event moveWithPointer({double clientX, double clientY, int pointer}); + html.Event upWithPointer({double clientX, double clientY, int pointer}); + + @override + html.Event down({double clientX, double clientY}) { + return downWithPointer( + pointer: 1, + clientX: clientX, + clientY: clientY, + ); + } + + @override + html.Event move({double clientX, double clientY}) { + return moveWithPointer( + pointer: 1, + clientX: clientX, + clientY: clientY, + ); + } + + @override + html.Event up({double clientX, double clientY}) { + return upWithPointer( + pointer: 1, + clientX: clientX, + clientY: clientY, + ); + } +} + +class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin implements PointerSupportDetector { _TouchEventContext() { _target = html.document.createElement('div'); } @@ -896,15 +974,6 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet }); } - @override - html.Event down({double clientX, double clientY}) { - return downWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); - } - html.Event downWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchstart', @@ -914,15 +983,6 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet ); } - @override - html.Event move({double clientX, double clientY}) { - return moveWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); - } - html.Event moveWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchmove', @@ -932,15 +992,6 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet ); } - @override - html.Event up({double clientX, double clientY}) { - return upWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); - } - html.Event upWithPointer({double clientX, double clientY, int pointer}) { return _createTouchEvent( 'touchend', @@ -951,52 +1002,7 @@ class _TouchEventContext extends _BasicEventContext implements PointerSupportDet } } -abstract class _ButtonedEventContext extends _BasicEventContext { - html.Event downWithButton({double clientX, double clientY, int button, int buttons}); - html.Event moveWithButton({double clientX, double clientY, int button, int buttons}); - html.Event upWithButton({double clientX, double clientY, int button}); - - html.Event hoverEvent({double clientX, double clientY}) { - return moveWithButton( - buttons: 0, - button: -1, - clientX: clientX, - clientY: clientY, - ); - } - - @override - html.Event down({double clientX, double clientY}) { - return downWithButton( - buttons: 1, - button: 0, - clientX: clientX, - clientY: clientY, - ); - } - - - @override - html.Event move({double clientX, double clientY}) { - return moveWithButton( - buttons: 1, - button: -1, - clientX: clientX, - clientY: clientY, - ); - } - - @override - html.Event up({double clientX, double clientY}) { - return upWithButton( - button: 1, - clientX: clientX, - clientY: clientY, - ); - } -} - -class _MouseEventContext extends _ButtonedEventContext implements PointerSupportDetector { +class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin implements PointerSupportDetector { @override String get name => 'MouseAdapter'; @@ -1063,7 +1069,7 @@ class _MouseEventContext extends _ButtonedEventContext implements PointerSupport } } -class _PointerEventContext extends _ButtonedEventContext implements PointerSupportDetector { +class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin, _MultiPointerEventMixin implements PointerSupportDetector { @override String get name => 'PointerAdapter'; @@ -1078,6 +1084,17 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo String pointerType = 'mouse'; + @override + html.Event downWithPointer({double clientX, double clientY, int pointer}) { + return downWithButtonPointer( + pointer: 1, + buttons: 1, + button: 0, + clientX: clientX, + clientY: clientY, + ); + } + @override html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { return downWithButtonPointer( @@ -1100,6 +1117,17 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo }); } + @override + html.Event moveWithPointer({double clientX, double clientY, int pointer}) { + return moveWithButtonPointer( + pointer: 1, + buttons: 1, + button: -1, + clientX: clientX, + clientY: clientY, + ); + } + @override html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { return moveWithButtonPointer( @@ -1122,6 +1150,16 @@ class _PointerEventContext extends _ButtonedEventContext implements PointerSuppo }); } + @override + html.Event upWithPointer({double clientX, double clientY, int pointer}) { + return upWithButtonPointer( + pointer: 1, + button: 0, + clientX: clientX, + clientY: clientY, + ); + } + @override html.Event upWithButton({double clientX, double clientY, int button}) { return upWithButtonPointer( From fdbe17133226ad7b73ac24b3d1012b2518c93f55 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 16 Dec 2019 18:50:13 -0800 Subject: [PATCH 17/28] Use array, and rewrite touch --- .../lib/src/engine/pointer_binding.dart | 216 +++++++++++------- 1 file changed, 136 insertions(+), 80 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 6e4920503c8ab..e5311e21022bd 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -351,35 +351,33 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { void setup() { _addPointerEventListener('pointerdown', (html.PointerEvent event) { final int device = event.pointerId; - final Iterable pointerData = _ensurePointerState(device) - .sanitizeDownEvent(buttons: event.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + final _PointerEventSanitizer state = _ensurePointerState(device); + for (_SanitizedDetails details in state.sanitizeDownEvent(buttons: event.buttons)) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); _addPointerEventListener('pointermove', (html.PointerEvent event) { final int device = event.pointerId; final _PointerEventSanitizer state = _ensurePointerState(device); - final Iterable pointerData = _expandEvents(event) - .expand((html.PointerEvent expandedEvent) { - return state - .sanitizeMoveEvent(buttons: expandedEvent.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: expandedEvent, details: details), - ); - }); + final List pointerData = []; + for (html.PointerEvent expandedEvent in _expandEvents(event)) { + for (_SanitizedDetails details in state.sanitizeMoveEvent(buttons: expandedEvent.buttons)) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } + } _callback(pointerData); }); _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; - final Iterable pointerData = _getPointerState(device) - .sanitizeUpEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + final _PointerEventSanitizer state = _getPointerState(device); + for (_SanitizedDetails details in state.sanitizeUpEvent()) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); @@ -387,11 +385,11 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { // be able to generate events (example: device is deactivated) _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; - final Iterable pointerData = _getPointerState(device) - .sanitizeCancelEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + final _PointerEventSanitizer state = _getPointerState(device); + for (_SanitizedDetails details in state.sanitizeCancelEvent()) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); @@ -407,11 +405,11 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { }); } - List _convertEventToPointerData({ + void _convertEventToPointerData({ + @required List data, @required html.PointerEvent event, @required _SanitizedDetails details, }) { - final List data = []; final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType); // We force `device: _mouseDeviceId` on mouse pointers because Wheel events // might come before any PointerEvents, and since wheel events don't contain @@ -432,7 +430,6 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { pressureMax: 1.0, tilt: _computeHighestTilt(event), ); - return data; } List _expandEvents(html.PointerEvent event) { @@ -470,6 +467,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { math.pi; } +typedef _TouchEventListener = dynamic Function(html.TouchEvent event); + /// Adapter to be used with browsers that support touch events. class _TouchAdapter extends _BaseAdapter { _TouchAdapter( @@ -478,59 +477,120 @@ class _TouchAdapter extends _BaseAdapter { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - bool _pressed = false; + final Set _pressedTouches = {}; + bool _isTouchPressed(int identifier) => _pressedTouches.contains(identifier); + void _pressTouch(int identifier) { _pressedTouches.add(identifier); } + void _unpressTouch(int identifier) { _pressedTouches.remove(identifier); } + + void _addTouchEventListener(String eventName, _TouchEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.TouchEvent touchEvent = event; + return handler(touchEvent); + }); + } @override void setup() { - addEventListener('touchstart', (html.Event event) { - _pressed = true; - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + _addTouchEventListener('touchstart', (html.TouchEvent event) { + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (!nowPressed) { + _pressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.down, + touch: touch, + pressed: true, + timeStamp: timeStamp, + ); + } + } + _callback(pointerData); }); - addEventListener('touchmove', (html.Event event) { + _addTouchEventListener('touchmove', (html.TouchEvent event) { event.preventDefault(); // Prevents standard overscroll on iOS/Webkit. - if (!_pressed) { - return; + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.move, + touch: touch, + pressed: true, + timeStamp: timeStamp, + ); + } } - _callback(_convertEventToPointerData(ui.PointerChange.move, event)); + _callback(pointerData); }); - addEventListener('touchend', (html.Event event) { + _addTouchEventListener('touchend', (html.TouchEvent event) { // On Safari Mobile, the keyboard does not show unless this line is // added. event.preventDefault(); - _pressed = false; - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _unpressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.up, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + } + } + _callback(pointerData); }); - addEventListener('touchcancel', (html.Event event) { - _pressed = false; - _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); + _addTouchEventListener('touchcancel', (html.TouchEvent event) { + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _unpressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.cancel, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + } + } }); } - List _convertEventToPointerData( - ui.PointerChange change, - html.TouchEvent event, - ) { - final List data = List(); - for (html.Touch touch in event.changedTouches) { - _pointerDataConverter.convert( - data, - change: change, - timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.touch, - signalKind: ui.PointerSignalKind.none, - device: touch.identifier, - physicalX: touch.client.x * ui.window.devicePixelRatio, - physicalY: touch.client.y * ui.window.devicePixelRatio, - buttons: _pressed ? _kPrimaryMouseButton : 0, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - ); - } - return data; + void _convertEventToPointerData({ + @required List data, + @required ui.PointerChange change, + @required html.Touch touch, + @required bool pressed, + @required Duration timeStamp, + }) { + _pointerDataConverter.convert( + data, + change: change, + timeStamp: timeStamp, + kind: ui.PointerDeviceKind.touch, + signalKind: ui.PointerSignalKind.none, + device: touch.identifier, + physicalX: touch.client.x * ui.window.devicePixelRatio, + physicalY: touch.client.y * ui.window.devicePixelRatio, + buttons: pressed ? _kPrimaryMouseButton : 0, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + ); } } @@ -556,29 +616,26 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { @override void setup() { _addMouseEventListener('mousedown', (html.MouseEvent event) { - final Iterable pointerData = _sanitizer - .sanitizeDownEvent(buttons: event.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + for (_SanitizedDetails details in _sanitizer.sanitizeDownEvent(buttons: event.buttons)) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); _addMouseEventListener('mousemove', (html.MouseEvent event) { - final Iterable pointerData = _sanitizer - .sanitizeMoveEvent(buttons: event.buttons) - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + for (_SanitizedDetails details in _sanitizer.sanitizeMoveEvent(buttons: event.buttons)) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); _addMouseEventListener('mouseup', (html.MouseEvent event) { - final Iterable pointerData = _sanitizer - .sanitizeUpEvent() - .expand((_SanitizedDetails details) => - _convertEventToPointerData(event: event, details: details), - ); + final List pointerData = []; + for (_SanitizedDetails details in _sanitizer.sanitizeUpEvent()) { + _convertEventToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); @@ -594,13 +651,13 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { }); } - List _convertEventToPointerData({ + void _convertEventToPointerData({ + @required List data, @required html.MouseEvent event, @required _SanitizedDetails details, }) { assert(event != null); assert(details != null); - List data = []; _pointerDataConverter.convert( data, change: details.change, @@ -615,6 +672,5 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { pressureMin: 0.0, pressureMax: 1.0, ); - return data; } } From f3251e23f3956aeea52487b988e0b6c4e4eab453 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 16 Dec 2019 19:17:51 -0800 Subject: [PATCH 18/28] Multi pointer tests --- .../lib/src/engine/pointer_binding.dart | 30 +-- .../test/engine/pointer_binding_test.dart | 253 ++++++++++-------- 2 files changed, 162 insertions(+), 121 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index e5311e21022bd..e0d0e28b050b0 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -328,16 +328,16 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - final Map _pointerStates = {}; + final Map _sanitizers = {}; - _PointerEventSanitizer _ensurePointerState(int device) { - return _pointerStates.putIfAbsent(device, () => _PointerEventSanitizer()); + _PointerEventSanitizer _ensureSanitizer(int device) { + return _sanitizers.putIfAbsent(device, () => _PointerEventSanitizer()); } - _PointerEventSanitizer _getPointerState(int device) { - final _PointerEventSanitizer state = _pointerStates[device]; - assert(state != null); - return state; + _PointerEventSanitizer _getSanitizer(int device) { + final _PointerEventSanitizer sanitizer = _sanitizers[device]; + assert(sanitizer != null); + return sanitizer; } void _addPointerEventListener(String eventName, _PointerEventListener handler) { @@ -352,8 +352,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointerdown', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer state = _ensurePointerState(device); - for (_SanitizedDetails details in state.sanitizeDownEvent(buttons: event.buttons)) { + final _PointerEventSanitizer sanitizer = _ensureSanitizer(device); + for (_SanitizedDetails details in sanitizer.sanitizeDownEvent(buttons: event.buttons)) { _convertEventToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); @@ -361,10 +361,10 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointermove', (html.PointerEvent event) { final int device = event.pointerId; - final _PointerEventSanitizer state = _ensurePointerState(device); + final _PointerEventSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; for (html.PointerEvent expandedEvent in _expandEvents(event)) { - for (_SanitizedDetails details in state.sanitizeMoveEvent(buttons: expandedEvent.buttons)) { + for (_SanitizedDetails details in sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons)) { _convertEventToPointerData(data: pointerData, event: event, details: details); } } @@ -374,8 +374,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer state = _getPointerState(device); - for (_SanitizedDetails details in state.sanitizeUpEvent()) { + final _PointerEventSanitizer sanitizer = _getSanitizer(device); + for (_SanitizedDetails details in sanitizer.sanitizeUpEvent()) { _convertEventToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); @@ -386,8 +386,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer state = _getPointerState(device); - for (_SanitizedDetails details in state.sanitizeCancelEvent()) { + final _PointerEventSanitizer sanitizer = _getSanitizer(device); + for (_SanitizedDetails details in sanitizer.sanitizeCancelEvent()) { _convertEventToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index beb812b7868ec..5968e2260f1d4 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -223,109 +223,114 @@ void main() { clientX: 10.0, clientY: 10.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].pointerIdentifier, equals(0)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].physicalX, equals(10.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[1].pointerIdentifier, equals(0)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(10.0)); + expect(packets[0].data[1].physicalY, equals(10.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); + packets.clear(); glassPane.dispatchEvent(context.hoverEvent( clientX: 20.0, clientY: 20.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[0].pointerIdentifier, equals(0)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20.0)); + expect(packets[0].data[0].physicalY, equals(20.0)); + expect(packets[0].data[0].physicalDeltaX, equals(10.0)); + expect(packets[0].data[0].physicalDeltaY, equals(10.0)); + packets.clear(); glassPane.dispatchEvent(context.down( clientX: 20.0, clientY: 20.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20.0)); + expect(packets[0].data[0].physicalY, equals(20.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + packets.clear(); glassPane.dispatchEvent(context.move( clientX: 40.0, clientY: 30.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40.0)); + expect(packets[0].data[0].physicalY, equals(30.0)); + expect(packets[0].data[0].physicalDeltaX, equals(20.0)); + expect(packets[0].data[0].physicalDeltaY, equals(10.0)); + packets.clear(); glassPane.dispatchEvent(context.up( clientX: 40.0, clientY: 30.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(40.0)); + expect(packets[0].data[0].physicalY, equals(30.0)); + expect(packets[0].data[0].physicalDeltaX, equals(0.0)); + expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + packets.clear(); glassPane.dispatchEvent(context.hoverEvent( clientX: 20.0, clientY: 10.0, )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[0].data[0].pointerIdentifier, equals(1)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20.0)); + expect(packets[0].data[0].physicalY, equals(10.0)); + expect(packets[0].data[0].physicalDeltaX, equals(-20.0)); + expect(packets[0].data[0].physicalDeltaY, equals(-20.0)); + packets.clear(); glassPane.dispatchEvent(context.down( clientX: 20.0, clientY: 10.0, )); - - expect(packets, hasLength(7)); - - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].pointerIdentifier, equals(0)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].physicalX, equals(10.0)); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].pointerIdentifier, equals(2)); + expect(packets[0].data[0].synthesized, equals(false)); + expect(packets[0].data[0].physicalX, equals(20.0)); expect(packets[0].data[0].physicalY, equals(10.0)); expect(packets[0].data[0].physicalDeltaX, equals(0.0)); expect(packets[0].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[0].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[0].data[1].pointerIdentifier, equals(0)); - expect(packets[0].data[1].synthesized, equals(false)); - expect(packets[0].data[1].physicalX, equals(10.0)); - expect(packets[0].data[1].physicalY, equals(10.0)); - expect(packets[0].data[1].physicalDeltaX, equals(0.0)); - expect(packets[0].data[1].physicalDeltaY, equals(0.0)); - - expect(packets[1].data, hasLength(1)); - expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[0].pointerIdentifier, equals(0)); - expect(packets[1].data[0].synthesized, equals(false)); - expect(packets[1].data[0].physicalX, equals(20.0)); - expect(packets[1].data[0].physicalY, equals(20.0)); - expect(packets[1].data[0].physicalDeltaX, equals(10.0)); - expect(packets[1].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[2].data, hasLength(1)); - expect(packets[2].data[0].change, equals(ui.PointerChange.down)); - expect(packets[2].data[0].pointerIdentifier, equals(1)); - expect(packets[2].data[0].synthesized, equals(false)); - expect(packets[2].data[0].physicalX, equals(20.0)); - expect(packets[2].data[0].physicalY, equals(20.0)); - expect(packets[2].data[0].physicalDeltaX, equals(0.0)); - expect(packets[2].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[3].data, hasLength(1)); - expect(packets[3].data[0].change, equals(ui.PointerChange.move)); - expect(packets[3].data[0].pointerIdentifier, equals(1)); - expect(packets[3].data[0].synthesized, equals(false)); - expect(packets[3].data[0].physicalX, equals(40.0)); - expect(packets[3].data[0].physicalY, equals(30.0)); - expect(packets[3].data[0].physicalDeltaX, equals(20.0)); - expect(packets[3].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[4].data, hasLength(1)); - expect(packets[4].data[0].change, equals(ui.PointerChange.up)); - expect(packets[4].data[0].pointerIdentifier, equals(1)); - expect(packets[4].data[0].synthesized, equals(false)); - expect(packets[4].data[0].physicalX, equals(40.0)); - expect(packets[4].data[0].physicalY, equals(30.0)); - expect(packets[4].data[0].physicalDeltaX, equals(0.0)); - expect(packets[4].data[0].physicalDeltaY, equals(0.0)); - - expect(packets[5].data, hasLength(1)); - expect(packets[5].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[5].data[0].pointerIdentifier, equals(1)); - expect(packets[5].data[0].synthesized, equals(false)); - expect(packets[5].data[0].physicalX, equals(20.0)); - expect(packets[5].data[0].physicalY, equals(10.0)); - expect(packets[5].data[0].physicalDeltaX, equals(-20.0)); - expect(packets[5].data[0].physicalDeltaY, equals(-20.0)); - - expect(packets[6].data, hasLength(1)); - expect(packets[6].data[0].change, equals(ui.PointerChange.down)); - expect(packets[6].data[0].pointerIdentifier, equals(2)); - expect(packets[6].data[0].synthesized, equals(false)); - expect(packets[6].data[0].physicalX, equals(20.0)); - expect(packets[6].data[0].physicalY, equals(10.0)); - expect(packets[6].data[0].physicalDeltaX, equals(0.0)); - expect(packets[6].data[0].physicalDeltaY, equals(0.0)); + packets.clear(); }); }); @@ -702,6 +707,44 @@ void main() { }); }); + // MULTIPOINTER ADAPTERS + + <_MultiPointerEventMixin>[_PointerEventContext(), _TouchEventContext()].forEach((_MultiPointerEventMixin context) { + test('${context.name} treats each pointer separately', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + glassPane.dispatchEvent(context.downWithPointer( + pointer: 2, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].device, equals(2)); + expect(packets[0].data[1].buttons, equals(1)); + packets.clear(); + + glassPane.dispatchEvent(context.downWithPointer( + pointer: 3, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].device, equals(3)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].device, equals(3)); + expect(packets[0].data[1].buttons, equals(1)); + packets.clear(); + }); + }); + // POINTER ADAPTER [_PointerEventContext()].forEach((_PointerEventContext context) { @@ -712,23 +755,10 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent((context - ..pointerType = 'touch' - ).downWithButtonPointer( + glassPane.dispatchEvent(context.downWithPointer( pointer: 1, - button: 0, - buttons: 1, - )); - - glassPane.dispatchEvent((context - ..pointerType = 'touch' - ).downWithButtonPointer( - pointer: 2, - button: 0, - buttons: 1, )); - - expect(packets, hasLength(2)); + expect(packets, hasLength(1)); // An add will be synthesized. expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -736,13 +766,20 @@ void main() { expect(packets[0].data[0].device, equals(1)); expect(packets[0].data[1].change, equals(ui.PointerChange.down)); expect(packets[0].data[1].device, equals(1)); + packets.clear(); + + glassPane.dispatchEvent(context.downWithPointer( + pointer: 2, + )); // An add will be synthesized. - expect(packets[1].data, hasLength(2)); - expect(packets[1].data[0].change, equals(ui.PointerChange.add)); - expect(packets[1].data[0].synthesized, equals(true)); - expect(packets[1].data[0].device, equals(2)); - expect(packets[1].data[1].change, equals(ui.PointerChange.down)); - expect(packets[1].data[1].device, equals(2)); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.add)); + expect(packets[0].data[0].synthesized, equals(true)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[1].device, equals(2)); + packets.clear(); }); }); @@ -1069,7 +1106,7 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp } } -class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin, _MultiPointerEventMixin implements PointerSupportDetector { +class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin implements PointerSupportDetector, _MultiPointerEventMixin { @override String get name => 'PointerAdapter'; @@ -1082,31 +1119,31 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin, @override bool get hasMouseEvents => false; - String pointerType = 'mouse'; - @override html.Event downWithPointer({double clientX, double clientY, int pointer}) { - return downWithButtonPointer( - pointer: 1, + return _downWithFullDetails( + pointer: pointer, buttons: 1, button: 0, clientX: clientX, clientY: clientY, + pointerType: 'touch', ); } @override html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { - return downWithButtonPointer( + return _downWithFullDetails( pointer: 1, buttons: buttons, button: button, clientX: clientX, clientY: clientY, + pointerType: 'mouse', ); } - html.Event downWithButtonPointer({double clientX, double clientY, int button, int buttons, int pointer}) { + html.Event _downWithFullDetails({double clientX, double clientY, int button, int buttons, int pointer, String pointerType}) { return html.PointerEvent('pointerdown', { 'pointerId': pointer, 'button': button, @@ -1119,27 +1156,29 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin, @override html.Event moveWithPointer({double clientX, double clientY, int pointer}) { - return moveWithButtonPointer( - pointer: 1, + return _moveWithFullDetails( + pointer: pointer, buttons: 1, button: -1, clientX: clientX, clientY: clientY, + pointerType: 'touch', ); } @override html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { - return moveWithButtonPointer( + return _moveWithFullDetails( pointer: 1, buttons: buttons, button: button, clientX: clientX, clientY: clientY, + pointerType: 'mouse', ); } - html.Event moveWithButtonPointer({double clientX, double clientY, int button, int buttons, int pointer}) { + html.Event _moveWithFullDetails({double clientX, double clientY, int button, int buttons, int pointer, String pointerType}) { return html.PointerEvent('pointermove', { 'pointerId': pointer, 'button': button, @@ -1152,25 +1191,27 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin, @override html.Event upWithPointer({double clientX, double clientY, int pointer}) { - return upWithButtonPointer( - pointer: 1, + return _upWithFullDetails( + pointer: pointer, button: 0, clientX: clientX, clientY: clientY, + pointerType: 'touch', ); } @override html.Event upWithButton({double clientX, double clientY, int button}) { - return upWithButtonPointer( + return _upWithFullDetails( pointer: 1, button: button, clientX: clientX, clientY: clientY, + pointerType: 'mouse', ); } - html.Event upWithButtonPointer({double clientX, double clientY, int button, int pointer}) { + html.Event _upWithFullDetails({double clientX, double clientY, int button, int pointer, String pointerType}) { return html.PointerEvent('pointerup', { 'pointerId': pointer, 'button': button, From 44d29ecbfd14cee84b297d6b29e233137097a455 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 16 Dec 2019 19:18:42 -0800 Subject: [PATCH 19/28] Add todo for cancel --- lib/web_ui/lib/src/engine/pointer_binding.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index e0d0e28b050b0..d664b6ec08ffd 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -383,6 +383,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) + // TODO(dkwingsmt): Add tests for cancel _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; @@ -551,6 +552,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(pointerData); }); + // TODO(dkwingsmt): Add tests for cancel _addTouchEventListener('touchcancel', (html.TouchEvent event) { final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); final List pointerData = []; From 859749e92c15ffa650fdbcf590147e3aeb28a6ec Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 15:50:06 -0800 Subject: [PATCH 20/28] Add remove events; test event generators --- .../lib/src/engine/pointer_binding.dart | 59 ++- .../lib/src/engine/pointer_converter.dart | 2 +- .../test/engine/pointer_binding_test.dart | 352 +++++++++++++++++- 3 files changed, 381 insertions(+), 32 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index d664b6ec08ffd..93907144ad927 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -256,7 +256,7 @@ class _SanitizedDetails { String toString() => '$runtimeType(change: $change, buttons: $buttons)'; } -class _PointerEventSanitizer { +class _ButtonSanitizer { int _pressedButtons = 0; // Transform html.PointerEvent.buttons to Flutter's PointerEvent buttons. @@ -328,18 +328,35 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - final Map _sanitizers = {}; + final Map _sanitizers = {}; - _PointerEventSanitizer _ensureSanitizer(int device) { - return _sanitizers.putIfAbsent(device, () => _PointerEventSanitizer()); + @visibleForTesting + Iterable debugTrackedDevices() => _sanitizers.keys; + + _ButtonSanitizer _ensureSanitizer(int device) { + return _sanitizers.putIfAbsent(device, () => _ButtonSanitizer()); } - _PointerEventSanitizer _getSanitizer(int device) { - final _PointerEventSanitizer sanitizer = _sanitizers[device]; + _ButtonSanitizer _getSanitizer(int device) { + final _ButtonSanitizer sanitizer = _sanitizers[device]; assert(sanitizer != null); return sanitizer; } + void _removePointerIfUnhoverable(List pointerData, html.PointerEvent event) { + if (event.pointerType == 'touch') { + _sanitizers.remove(event.pointerId); + _convertEventToPointerData( + data: pointerData, + event: event, + details: _SanitizedDetails( + buttons: 0, + change: ui.PointerChange.remove, + ), + ); + } + } + void _addPointerEventListener(String eventName, _PointerEventListener handler) { addEventListener(eventName, (html.Event event) { final html.PointerEvent pointerEvent = event; @@ -352,7 +369,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointerdown', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer sanitizer = _ensureSanitizer(device); + final _ButtonSanitizer sanitizer = _ensureSanitizer(device); for (_SanitizedDetails details in sanitizer.sanitizeDownEvent(buttons: event.buttons)) { _convertEventToPointerData(data: pointerData, event: event, details: details); } @@ -361,7 +378,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointermove', (html.PointerEvent event) { final int device = event.pointerId; - final _PointerEventSanitizer sanitizer = _ensureSanitizer(device); + final _ButtonSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; for (html.PointerEvent expandedEvent in _expandEvents(event)) { for (_SanitizedDetails details in sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons)) { @@ -371,26 +388,28 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _callback(pointerData); }); - _addPointerEventListener('pointerup', (html.PointerEvent event) { + _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer sanitizer = _getSanitizer(device); + final _ButtonSanitizer sanitizer = _getSanitizer(device); for (_SanitizedDetails details in sanitizer.sanitizeUpEvent()) { _convertEventToPointerData(data: pointerData, event: event, details: details); } + _removePointerIfUnhoverable(pointerData, event); _callback(pointerData); }); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) // TODO(dkwingsmt): Add tests for cancel - _addPointerEventListener('pointercancel', (html.PointerEvent event) { + _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _PointerEventSanitizer sanitizer = _getSanitizer(device); + final _ButtonSanitizer sanitizer = _getSanitizer(device); for (_SanitizedDetails details in sanitizer.sanitizeCancelEvent()) { _convertEventToPointerData(data: pointerData, event: event, details: details); } + _removePointerIfUnhoverable(pointerData, event); _callback(pointerData); }); @@ -547,6 +566,13 @@ class _TouchAdapter extends _BaseAdapter { pressed: false, timeStamp: timeStamp, ); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.remove, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); } } _callback(pointerData); @@ -567,6 +593,13 @@ class _TouchAdapter extends _BaseAdapter { pressed: false, timeStamp: timeStamp, ); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.remove, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); } } }); @@ -606,7 +639,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { PointerDataConverter _pointerDataConverter ) : super(callback, glassPaneElement, _pointerDataConverter); - final _PointerEventSanitizer _sanitizer = _PointerEventSanitizer(); + final _ButtonSanitizer _sanitizer = _ButtonSanitizer(); void _addMouseEventListener(String eventName, _MouseEventListener handler) { addEventListener(eventName, (html.Event event) { diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index fbedc00748c4f..c0cac2d717a58 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -469,7 +469,6 @@ class PointerDataConverter { final _PointerState state = _pointers[device]; assert(!state.down); assert(!_locationHasChanged(device, physicalX, physicalY)); - _pointers.remove(device); result.add( _generateCompletePointerData( timeStamp: timeStamp, @@ -498,6 +497,7 @@ class PointerDataConverter { scrollDeltaY: scrollDeltaY, ) ); + _pointers.remove(device); break; } } else { diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 5968e2260f1d4..08c67fc4c0b48 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -20,6 +20,207 @@ void main() { ui.window.onPointerDataPacket = null; }); + test('_PointerEventContext generates expected events', () { + html.PointerEvent expectCorrectType(html.Event e) { + expect(e.runtimeType, equals(html.PointerEvent)); + return e; + } + + final _PointerEventContext context = _PointerEventContext(); + html.PointerEvent event; + + event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + expect(event.type, equals('pointerdown')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(0)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(100)); + expect(event.client.y, equals(101)); + + event = expectCorrectType(context.downWithButton(clientX: 110, clientY: 111, button: 2, buttons: 2)); + expect(event.type, equals('pointerdown')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(2)); + expect(event.buttons, equals(2)); + expect(event.client.x, equals(110)); + expect(event.client.y, equals(111)); + + event = expectCorrectType(context.downWithPointer(clientX: 120, clientY: 121, pointer: 100)); + expect(event.type, equals('pointerdown')); + expect(event.pointerId, equals(100)); + expect(event.button, equals(0)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(120)); + expect(event.client.y, equals(121)); + + event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(-1)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(200)); + expect(event.client.y, equals(201)); + + event = expectCorrectType(context.moveWithButton(clientX: 210, clientY: 211, button: 2, buttons: 6)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(2)); + expect(event.buttons, equals(6)); + expect(event.client.x, equals(210)); + expect(event.client.y, equals(211)); + + event = expectCorrectType(context.moveWithPointer(clientX: 220, clientY: 221, pointer: 101)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(101)); + expect(event.button, equals(-1)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(220)); + expect(event.client.y, equals(221)); + + event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + expect(event.type, equals('pointerup')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(0)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(300)); + expect(event.client.y, equals(301)); + + event = expectCorrectType(context.upWithButton(clientX: 310, clientY: 311, button: 2)); + expect(event.type, equals('pointerup')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(2)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(310)); + expect(event.client.y, equals(311)); + + event = expectCorrectType(context.upWithPointer(clientX: 320, clientY: 321, pointer: 102)); + expect(event.type, equals('pointerup')); + expect(event.pointerId, equals(102)); + expect(event.button, equals(0)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(320)); + expect(event.client.y, equals(321)); + + event = expectCorrectType(context.hover(clientX: 400, clientY: 401)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(-1)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(400)); + expect(event.client.y, equals(401)); + }); + + test('_TouchEventContext generates expected events', () { + html.TouchEvent expectCorrectType(html.Event e) { + expect(e.runtimeType, equals(html.TouchEvent)); + return e; + } + + final _TouchEventContext context = _TouchEventContext(); + html.TouchEvent event; + + event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + expect(event.type, equals('touchstart')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(1)); + expect(event.changedTouches[0].client.x, equals(100)); + expect(event.changedTouches[0].client.y, equals(101)); + + event = expectCorrectType(context.downWithPointer(clientX: 110, clientY: 111, pointer: 100)); + expect(event.type, equals('touchstart')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(100)); + expect(event.changedTouches[0].client.x, equals(110)); + expect(event.changedTouches[0].client.y, equals(111)); + + event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + expect(event.type, equals('touchmove')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(1)); + expect(event.changedTouches[0].client.x, equals(200)); + expect(event.changedTouches[0].client.y, equals(201)); + + event = expectCorrectType(context.moveWithPointer(clientX: 210, clientY: 211, pointer: 101)); + expect(event.type, equals('touchmove')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(101)); + expect(event.changedTouches[0].client.x, equals(210)); + expect(event.changedTouches[0].client.y, equals(211)); + + event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + expect(event.type, equals('touchend')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(1)); + expect(event.changedTouches[0].client.x, equals(300)); + expect(event.changedTouches[0].client.y, equals(301)); + + event = expectCorrectType(context.upWithPointer(clientX: 310, clientY: 311, pointer: 102)); + expect(event.type, equals('touchend')); + expect(event.changedTouches.length, equals(1)); + expect(event.changedTouches[0].identifier, equals(102)); + expect(event.changedTouches[0].client.x, equals(310)); + expect(event.changedTouches[0].client.y, equals(311)); + }); + + test('_MouseEventContext generates expected events', () { + html.MouseEvent expectCorrectType(html.Event e) { + expect(e.runtimeType, equals(html.MouseEvent)); + return e; + } + + final _MouseEventContext context = _MouseEventContext(); + html.MouseEvent event; + + event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + expect(event.type, equals('mousedown')); + expect(event.button, equals(0)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(100)); + expect(event.client.y, equals(101)); + + event = expectCorrectType(context.downWithButton(clientX: 110, clientY: 111, button: 2, buttons: 2)); + expect(event.type, equals('mousedown')); + expect(event.button, equals(2)); + expect(event.buttons, equals(2)); + expect(event.client.x, equals(110)); + expect(event.client.y, equals(111)); + + event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + expect(event.type, equals('mousemove')); + expect(event.button, equals(0)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(200)); + expect(event.client.y, equals(201)); + + event = expectCorrectType(context.moveWithButton(clientX: 210, clientY: 211, button: 2, buttons: 6)); + expect(event.type, equals('mousemove')); + expect(event.button, equals(2)); + expect(event.buttons, equals(6)); + expect(event.client.x, equals(210)); + expect(event.client.y, equals(211)); + + event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + expect(event.type, equals('mouseup')); + expect(event.button, equals(0)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(300)); + expect(event.client.y, equals(301)); + + event = expectCorrectType(context.upWithButton(clientX: 310, clientY: 311, button: 2)); + expect(event.type, equals('mouseup')); + expect(event.button, equals(2)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(310)); + expect(event.client.y, equals(311)); + + event = expectCorrectType(context.hover(clientX: 400, clientY: 401)); + expect(event.type, equals('mousemove')); + expect(event.button, equals(0)); + expect(event.buttons, equals(0)); + expect(event.client.x, equals(400)); + expect(event.client.y, equals(401)); + }); + // ALL ADAPTERS [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()].forEach((_BasicEventContext context) { @@ -65,7 +266,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.hoverEvent()); + glassPane.dispatchEvent(context.hover()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -219,7 +420,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.hoverEvent( + glassPane.dispatchEvent(context.hover( clientX: 10.0, clientY: 10.0, )); @@ -242,7 +443,7 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.hoverEvent( + glassPane.dispatchEvent(context.hover( clientX: 20.0, clientY: 20.0, )); @@ -302,7 +503,7 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.hoverEvent( + glassPane.dispatchEvent(context.hover( clientX: 20.0, clientY: 10.0, )); @@ -344,7 +545,7 @@ void main() { // Add and hover - glassPane.dispatchEvent(context.hoverEvent( + glassPane.dispatchEvent(context.hover( clientX: 10, clientY: 11, )); @@ -379,7 +580,7 @@ void main() { packets.clear(); glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 1, clientX: 20.0, clientY: 21.0, @@ -424,7 +625,7 @@ void main() { packets.clear(); glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 2, clientX: 30.0, clientY: 31.0, @@ -469,7 +670,7 @@ void main() { packets.clear(); glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 4, clientX: 40.0, clientY: 41.0, @@ -592,7 +793,7 @@ void main() { packets.clear(); glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 2, clientX: 20.0, clientY: 21.0, @@ -608,7 +809,7 @@ void main() { glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 2, clientX: 20.0, clientY: 21.0, @@ -672,7 +873,7 @@ void main() { // in "buttons", probably because the "press" gesture was absorbed by // dismissing the context menu. glassPane.dispatchEvent(context.moveWithButton( - button: -1, + button: context.unchangedButton, buttons: 3, )); expect(packets, hasLength(1)); @@ -719,28 +920,125 @@ void main() { glassPane.dispatchEvent(context.downWithPointer( pointer: 2, + clientX: 100, + clientY: 101, )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); expect(packets[0].data[0].synthesized, equals(true)); expect(packets[0].data[0].device, equals(2)); - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); + expect(packets[0].data[0].physicalX, equals(100)); + expect(packets[0].data[0].physicalY, equals(101)); + expect(packets[0].data[1].device, equals(2)); expect(packets[0].data[1].buttons, equals(1)); + expect(packets[0].data[1].physicalX, equals(100)); + expect(packets[0].data[1].physicalY, equals(101)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); glassPane.dispatchEvent(context.downWithPointer( pointer: 3, + clientX: 200, + clientY: 201, )); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); expect(packets[0].data[0].synthesized, equals(true)); expect(packets[0].data[0].device, equals(3)); + expect(packets[0].data[0].physicalX, equals(200)); + expect(packets[0].data[0].physicalY, equals(201)); + expect(packets[0].data[1].change, equals(ui.PointerChange.down)); expect(packets[0].data[1].device, equals(3)); expect(packets[0].data[1].buttons, equals(1)); + expect(packets[0].data[1].physicalX, equals(200)); + expect(packets[0].data[1].physicalY, equals(201)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); + packets.clear(); + + glassPane.dispatchEvent(context.moveWithPointer( + pointer: 3, + clientX: 300, + clientY: 302, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].device, equals(3)); + expect(packets[0].data[0].buttons, equals(1)); + expect(packets[0].data[0].physicalX, equals(300)); + expect(packets[0].data[0].physicalY, equals(302)); + expect(packets[0].data[0].physicalDeltaX, equals(100)); + expect(packets[0].data[0].physicalDeltaY, equals(101)); + packets.clear(); + + glassPane.dispatchEvent(context.moveWithPointer( + pointer: 2, + clientX: 400, + clientY: 402, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[0].buttons, equals(1)); + expect(packets[0].data[0].physicalX, equals(400)); + expect(packets[0].data[0].physicalY, equals(402)); + expect(packets[0].data[0].physicalDeltaX, equals(300)); + expect(packets[0].data[0].physicalDeltaY, equals(301)); + packets.clear(); + + glassPane.dispatchEvent(context.upWithPointer( + pointer: 3, + clientX: 300, + clientY: 302, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].device, equals(3)); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(300)); + expect(packets[0].data[0].physicalY, equals(302)); + expect(packets[0].data[0].physicalDeltaX, equals(0)); + expect(packets[0].data[0].physicalDeltaY, equals(0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(3)); + expect(packets[0].data[1].buttons, equals(0)); + expect(packets[0].data[1].physicalX, equals(300)); + expect(packets[0].data[1].physicalY, equals(302)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); + packets.clear(); + + glassPane.dispatchEvent(context.upWithPointer( + pointer: 2, + clientX: 400, + clientY: 402, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.up)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(400)); + expect(packets[0].data[0].physicalY, equals(402)); + expect(packets[0].data[0].physicalDeltaX, equals(0)); + expect(packets[0].data[0].physicalDeltaY, equals(0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(2)); + expect(packets[0].data[1].buttons, equals(0)); + expect(packets[0].data[1].physicalX, equals(400)); + expect(packets[0].data[1].physicalY, equals(402)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); }); }); @@ -840,7 +1138,7 @@ void main() { clientY: 30.0, )); expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); + expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); expect(packets[0].data[0].pointerIdentifier, equals(1)); expect(packets[0].data[0].synthesized, equals(false)); @@ -848,6 +1146,14 @@ void main() { expect(packets[0].data[0].physicalY, equals(30.0)); expect(packets[0].data[0].physicalDeltaX, equals(0.0)); expect(packets[0].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].pointerIdentifier, equals(1)); + expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].physicalX, equals(40.0)); + expect(packets[0].data[1].physicalY, equals(30.0)); + expect(packets[0].data[1].physicalDeltaX, equals(0.0)); + expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); glassPane.dispatchEvent(context.downWithPointer( @@ -886,14 +1192,18 @@ abstract class _BasicEventContext implements PointerSupportDetector { } mixin _ButtonedEventMixin on _BasicEventContext { + // Value used as the `button` field when mouse moves or hovers without change + // in `buttons`. + int get unchangedButton; + html.Event downWithButton({double clientX, double clientY, int button, int buttons}); html.Event moveWithButton({double clientX, double clientY, int button, int buttons}); html.Event upWithButton({double clientX, double clientY, int button}); - html.Event hoverEvent({double clientX, double clientY}) { + html.Event hover({double clientX, double clientY}) { return moveWithButton( buttons: 0, - button: -1, + button: unchangedButton, clientX: clientX, clientY: clientY, ); @@ -914,7 +1224,7 @@ mixin _ButtonedEventMixin on _BasicEventContext { html.Event move({double clientX, double clientY}) { return moveWithButton( buttons: 1, - button: -1, + button: unchangedButton, clientX: clientX, clientY: clientY, ); @@ -923,7 +1233,7 @@ mixin _ButtonedEventMixin on _BasicEventContext { @override html.Event up({double clientX, double clientY}) { return upWithButton( - button: 1, + button: 0, clientX: clientX, clientY: clientY, ); @@ -1052,6 +1362,9 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp @override bool get hasMouseEvents => true; + @override + int get unchangedButton => 0; + @override html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { return _createMouseEvent( @@ -1119,6 +1432,9 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i @override bool get hasMouseEvents => false; + @override + int get unchangedButton => -1; + @override html.Event downWithPointer({double clientX, double clientY, int pointer}) { return _downWithFullDetails( @@ -1159,7 +1475,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i return _moveWithFullDetails( pointer: pointer, buttons: 1, - button: -1, + button: unchangedButton, clientX: clientX, clientY: clientY, pointerType: 'touch', From e9778e23d367a75c6984d24f300411701a9f6996 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 16:23:57 -0800 Subject: [PATCH 21/28] Fix mouse event generator --- .../test/engine/pointer_binding_test.dart | 252 ++++++++++++------ 1 file changed, 164 insertions(+), 88 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 08c67fc4c0b48..65c10bb473bfa 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -10,6 +10,14 @@ import 'package:ui/ui.dart' as ui; import 'package:test/test.dart'; +const int _kPrimaryMouseButton = 0x1; +const int _kSecondaryMouseButton = 0x2; +const int _kMiddleMouseButton =0x4; +const int _kBackwardMouseButton = 0x8; +const int _kForwardMouseButton = 0x10; + +const int _kNoButtonChange = -1; + void main() { html.Element glassPane = domRenderer.glassPaneElement; @@ -29,7 +37,7 @@ void main() { final _PointerEventContext context = _PointerEventContext(); html.PointerEvent event; - event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101)); expect(event.type, equals('pointerdown')); expect(event.pointerId, equals(1)); expect(event.button, equals(0)); @@ -37,7 +45,7 @@ void main() { expect(event.client.x, equals(100)); expect(event.client.y, equals(101)); - event = expectCorrectType(context.downWithButton(clientX: 110, clientY: 111, button: 2, buttons: 2)); + event = expectCorrectType(context.mouseDown(clientX: 110, clientY: 111, button: 2, buttons: 2)); expect(event.type, equals('pointerdown')); expect(event.pointerId, equals(1)); expect(event.button, equals(2)); @@ -53,7 +61,7 @@ void main() { expect(event.client.x, equals(120)); expect(event.client.y, equals(121)); - event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201)); expect(event.type, equals('pointermove')); expect(event.pointerId, equals(1)); expect(event.button, equals(-1)); @@ -61,14 +69,30 @@ void main() { expect(event.client.x, equals(200)); expect(event.client.y, equals(201)); - event = expectCorrectType(context.moveWithButton(clientX: 210, clientY: 211, button: 2, buttons: 6)); + event = expectCorrectType(context.mouseMove(clientX: 210, clientY: 211, button: _kNoButtonChange, buttons: 6)); expect(event.type, equals('pointermove')); expect(event.pointerId, equals(1)); - expect(event.button, equals(2)); + expect(event.button, equals(-1)); expect(event.buttons, equals(6)); expect(event.client.x, equals(210)); expect(event.client.y, equals(211)); + event = expectCorrectType(context.mouseMove(clientX: 212, clientY: 213, button: 2, buttons: 6)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(2)); + expect(event.buttons, equals(6)); + expect(event.client.x, equals(212)); + expect(event.client.y, equals(213)); + + event = expectCorrectType(context.mouseMove(clientX: 214, clientY: 215, button: 2, buttons: 1)); + expect(event.type, equals('pointermove')); + expect(event.pointerId, equals(1)); + expect(event.button, equals(2)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(214)); + expect(event.client.y, equals(215)); + event = expectCorrectType(context.moveWithPointer(clientX: 220, clientY: 221, pointer: 101)); expect(event.type, equals('pointermove')); expect(event.pointerId, equals(101)); @@ -77,7 +101,7 @@ void main() { expect(event.client.x, equals(220)); expect(event.client.y, equals(221)); - event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301)); expect(event.type, equals('pointerup')); expect(event.pointerId, equals(1)); expect(event.button, equals(0)); @@ -85,7 +109,7 @@ void main() { expect(event.client.x, equals(300)); expect(event.client.y, equals(301)); - event = expectCorrectType(context.upWithButton(clientX: 310, clientY: 311, button: 2)); + event = expectCorrectType(context.mouseUp(clientX: 310, clientY: 311, button: 2)); expect(event.type, equals('pointerup')); expect(event.pointerId, equals(1)); expect(event.button, equals(2)); @@ -119,7 +143,7 @@ void main() { final _TouchEventContext context = _TouchEventContext(); html.TouchEvent event; - event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101)); expect(event.type, equals('touchstart')); expect(event.changedTouches.length, equals(1)); expect(event.changedTouches[0].identifier, equals(1)); @@ -133,7 +157,7 @@ void main() { expect(event.changedTouches[0].client.x, equals(110)); expect(event.changedTouches[0].client.y, equals(111)); - event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201)); expect(event.type, equals('touchmove')); expect(event.changedTouches.length, equals(1)); expect(event.changedTouches[0].identifier, equals(1)); @@ -147,7 +171,7 @@ void main() { expect(event.changedTouches[0].client.x, equals(210)); expect(event.changedTouches[0].client.y, equals(211)); - event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301)); expect(event.type, equals('touchend')); expect(event.changedTouches.length, equals(1)); expect(event.changedTouches[0].identifier, equals(1)); @@ -171,42 +195,56 @@ void main() { final _MouseEventContext context = _MouseEventContext(); html.MouseEvent event; - event = expectCorrectType(context.down(clientX: 100, clientY: 101)); + event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101)); expect(event.type, equals('mousedown')); expect(event.button, equals(0)); expect(event.buttons, equals(1)); expect(event.client.x, equals(100)); expect(event.client.y, equals(101)); - event = expectCorrectType(context.downWithButton(clientX: 110, clientY: 111, button: 2, buttons: 2)); + event = expectCorrectType(context.mouseDown(clientX: 110, clientY: 111, button: 2, buttons: 2)); expect(event.type, equals('mousedown')); expect(event.button, equals(2)); expect(event.buttons, equals(2)); expect(event.client.x, equals(110)); expect(event.client.y, equals(111)); - event = expectCorrectType(context.move(clientX: 200, clientY: 201)); + event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201)); expect(event.type, equals('mousemove')); expect(event.button, equals(0)); expect(event.buttons, equals(1)); expect(event.client.x, equals(200)); expect(event.client.y, equals(201)); - event = expectCorrectType(context.moveWithButton(clientX: 210, clientY: 211, button: 2, buttons: 6)); + event = expectCorrectType(context.mouseMove(clientX: 210, clientY: 211, button: _kNoButtonChange, buttons: 6)); expect(event.type, equals('mousemove')); - expect(event.button, equals(2)); + expect(event.button, equals(0)); expect(event.buttons, equals(6)); expect(event.client.x, equals(210)); expect(event.client.y, equals(211)); - event = expectCorrectType(context.up(clientX: 300, clientY: 301)); + event = expectCorrectType(context.mouseMove(clientX: 212, clientY: 213, button: 2, buttons: 6)); + expect(event.type, equals('mousedown')); + expect(event.button, equals(2)); + expect(event.buttons, equals(6)); + expect(event.client.x, equals(212)); + expect(event.client.y, equals(213)); + + event = expectCorrectType(context.mouseMove(clientX: 214, clientY: 215, button: 2, buttons: 1)); + expect(event.type, equals('mouseup')); + expect(event.button, equals(2)); + expect(event.buttons, equals(1)); + expect(event.client.x, equals(214)); + expect(event.client.y, equals(215)); + + event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301)); expect(event.type, equals('mouseup')); expect(event.button, equals(0)); expect(event.buttons, equals(0)); expect(event.client.x, equals(300)); expect(event.client.y, equals(301)); - event = expectCorrectType(context.upWithButton(clientX: 310, clientY: 311, button: 2)); + event = expectCorrectType(context.mouseUp(clientX: 310, clientY: 311, button: 2)); expect(event.type, equals('mouseup')); expect(event.button, equals(2)); expect(event.buttons, equals(0)); @@ -231,7 +269,7 @@ void main() { receivedPacket = packet; }; - glassPane.dispatchEvent(context.down()); + glassPane.dispatchEvent(context.primaryDown()); expect(receivedPacket, isNotNull); expect(receivedPacket.data[0].buttons, equals(1)); @@ -246,7 +284,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.down()); + glassPane.dispatchEvent(context.primaryDown()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -285,9 +323,9 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.down()); + glassPane.dispatchEvent(context.primaryDown()); - glassPane.dispatchEvent(context.down()); + glassPane.dispatchEvent(context.primaryDown()); expect(packets, hasLength(2)); // An add will be synthesized. @@ -324,7 +362,7 @@ void main() { deltaY: 10, )); - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 0, buttons: 1, clientX: 20.0, @@ -458,7 +496,7 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(10.0)); packets.clear(); - glassPane.dispatchEvent(context.down( + glassPane.dispatchEvent(context.primaryDown( clientX: 20.0, clientY: 20.0, )); @@ -473,7 +511,7 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.move( + glassPane.dispatchEvent(context.primaryMove( clientX: 40.0, clientY: 30.0, )); @@ -488,7 +526,7 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(10.0)); packets.clear(); - glassPane.dispatchEvent(context.up( + glassPane.dispatchEvent(context.primaryUp( clientX: 40.0, clientY: 30.0, )); @@ -518,7 +556,7 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(-20.0)); packets.clear(); - glassPane.dispatchEvent(context.down( + glassPane.dispatchEvent(context.primaryDown( clientX: 20.0, clientY: 10.0, )); @@ -564,7 +602,7 @@ void main() { expect(packets[0].data[1].buttons, equals(0)); packets.clear(); - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 0, buttons: 1, clientX: 10.0, @@ -579,8 +617,8 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 1, clientX: 20.0, clientY: 21.0, @@ -594,7 +632,7 @@ void main() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 0, clientX: 20.0, clientY: 21.0, @@ -609,7 +647,7 @@ void main() { packets.clear(); // Drag with secondary button - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 20.0, @@ -624,8 +662,8 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 2, clientX: 30.0, clientY: 31.0, @@ -639,7 +677,7 @@ void main() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 2, clientX: 30.0, clientY: 31.0, @@ -654,7 +692,7 @@ void main() { packets.clear(); // Drag with middle button - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 1, buttons: 4, clientX: 30.0, @@ -669,8 +707,8 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 4, clientX: 40.0, clientY: 41.0, @@ -684,7 +722,7 @@ void main() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 1, clientX: 40.0, clientY: 41.0, @@ -709,7 +747,7 @@ void main() { }; // Press LMB. - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 0, buttons: 1, )); @@ -724,7 +762,7 @@ void main() { packets.clear(); // Press MMB. - glassPane.dispatchEvent(context.moveWithButton( + glassPane.dispatchEvent(context.mouseMove( button: 1, buttons: 5, )); @@ -736,7 +774,7 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(context.moveWithButton( + glassPane.dispatchEvent(context.mouseMove( button: 0, buttons: 4, )); @@ -748,7 +786,7 @@ void main() { packets.clear(); // Release MMB. - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 1, )); expect(packets, hasLength(1)); @@ -771,7 +809,7 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10, @@ -792,8 +830,8 @@ void main() { expect(packets[0].data[1].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 2, clientX: 20.0, clientY: 21.0, @@ -808,8 +846,8 @@ void main() { packets.clear(); - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 2, clientX: 20.0, clientY: 21.0, @@ -824,7 +862,7 @@ void main() { packets.clear(); - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 2, clientX: 20.0, clientY: 21.0, @@ -855,7 +893,7 @@ void main() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.downWithButton( + glassPane.dispatchEvent(context.mouseDown( button: 2, buttons: 2, )); @@ -872,8 +910,8 @@ void main() { // Press LMB. The event will have "button: -1" here, despite the change // in "buttons", probably because the "press" gesture was absorbed by // dismissing the context menu. - glassPane.dispatchEvent(context.moveWithButton( - button: context.unchangedButton, + glassPane.dispatchEvent(context.mouseMove( + button: _kNoButtonChange, buttons: 3, )); expect(packets, hasLength(1)); @@ -884,7 +922,7 @@ void main() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(context.moveWithButton( + glassPane.dispatchEvent(context.mouseMove( button: 0, buttons: 2, )); @@ -896,7 +934,7 @@ void main() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(context.upWithButton( + glassPane.dispatchEvent(context.mouseUp( button: 2, )); expect(packets, hasLength(1)); @@ -1186,32 +1224,52 @@ void main() { abstract class _BasicEventContext implements PointerSupportDetector { String get name; - html.Event down({double clientX, double clientY}); - html.Event move({double clientX, double clientY}); - html.Event up({double clientX, double clientY}); + // Generate an event that is: + // + // * For mouse, a left click + // * For touch, a touch down + html.Event primaryDown({double clientX, double clientY}); + + + // Generate an event that is: + // + // * For mouse, a drag with LMB down + // * For touch, a touch drag + html.Event primaryMove({double clientX, double clientY}); + + + // Generate an event that is: + // + // * For mouse, release LMB + // * For touch, a touch up + html.Event primaryUp({double clientX, double clientY}); } mixin _ButtonedEventMixin on _BasicEventContext { - // Value used as the `button` field when mouse moves or hovers without change - // in `buttons`. - int get unchangedButton; + // Generate an event that is a mouse down with the specific buttons. + html.Event mouseDown({double clientX, double clientY, int button, int buttons}); + + // Generate an event that is a mouse drag with the specific buttons, or button + // changes during the drag. + // + // If there is no button change, assign `button` with _kNoButtonChange. + html.Event mouseMove({double clientX, double clientY, int button, int buttons}); - html.Event downWithButton({double clientX, double clientY, int button, int buttons}); - html.Event moveWithButton({double clientX, double clientY, int button, int buttons}); - html.Event upWithButton({double clientX, double clientY, int button}); + // Generate an event that releases all mouse buttons. + html.Event mouseUp({double clientX, double clientY, int button}); html.Event hover({double clientX, double clientY}) { - return moveWithButton( + return mouseMove( buttons: 0, - button: unchangedButton, + button: _kNoButtonChange, clientX: clientX, clientY: clientY, ); } @override - html.Event down({double clientX, double clientY}) { - return downWithButton( + html.Event primaryDown({double clientX, double clientY}) { + return mouseDown( buttons: 1, button: 0, clientX: clientX, @@ -1221,18 +1279,18 @@ mixin _ButtonedEventMixin on _BasicEventContext { @override - html.Event move({double clientX, double clientY}) { - return moveWithButton( + html.Event primaryMove({double clientX, double clientY}) { + return mouseMove( buttons: 1, - button: unchangedButton, + button: _kNoButtonChange, clientX: clientX, clientY: clientY, ); } @override - html.Event up({double clientX, double clientY}) { - return upWithButton( + html.Event primaryUp({double clientX, double clientY}) { + return mouseUp( button: 0, clientX: clientX, clientY: clientY, @@ -1246,7 +1304,7 @@ mixin _MultiPointerEventMixin on _BasicEventContext { html.Event upWithPointer({double clientX, double clientY, int pointer}); @override - html.Event down({double clientX, double clientY}) { + html.Event primaryDown({double clientX, double clientY}) { return downWithPointer( pointer: 1, clientX: clientX, @@ -1255,7 +1313,7 @@ mixin _MultiPointerEventMixin on _BasicEventContext { } @override - html.Event move({double clientX, double clientY}) { + html.Event primaryMove({double clientX, double clientY}) { return moveWithPointer( pointer: 1, clientX: clientX, @@ -1264,7 +1322,7 @@ mixin _MultiPointerEventMixin on _BasicEventContext { } @override - html.Event up({double clientX, double clientY}) { + html.Event primaryUp({double clientX, double clientY}) { return upWithPointer( pointer: 1, clientX: clientX, @@ -1362,11 +1420,26 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp @override bool get hasMouseEvents => true; - @override - int get unchangedButton => 0; + static int _convertButtonToButtons(int button) { + switch(button) { + case 0: + return _kPrimaryMouseButton; + case 1: + return _kSecondaryMouseButton; + case 2: + return _kMiddleMouseButton; + case 3: + return _kBackwardMouseButton; + case 4: + return _kForwardMouseButton; + default: + assert(false, 'Unexpected button $button.'); + return -1; + } + } @override - html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { + html.Event mouseDown({double clientX, double clientY, int button, int buttons}) { return _createMouseEvent( 'mousedown', buttons: buttons, @@ -1377,18 +1450,24 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp } @override - html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { + html.Event mouseMove({double clientX, double clientY, int button, int buttons}) { + final bool hasButtonChange = button != _kNoButtonChange; + final bool changeIsButtonDown = hasButtonChange && (buttons & _convertButtonToButtons(button)) != 0; + final String adjustedType = !hasButtonChange ? 'mousemove' : + changeIsButtonDown ? 'mousedown' : + 'mouseup'; + final int adjustedButton = hasButtonChange ? button : 0; return _createMouseEvent( - 'mousemove', + adjustedType, buttons: buttons, - button: button, + button: adjustedButton, clientX: clientX, clientY: clientY, ); } @override - html.Event upWithButton({double clientX, double clientY, int button}) { + html.Event mouseUp({double clientX, double clientY, int button}) { return _createMouseEvent( 'mouseup', buttons: 0, @@ -1432,9 +1511,6 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i @override bool get hasMouseEvents => false; - @override - int get unchangedButton => -1; - @override html.Event downWithPointer({double clientX, double clientY, int pointer}) { return _downWithFullDetails( @@ -1448,7 +1524,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i } @override - html.Event downWithButton({double clientX, double clientY, int button, int buttons}) { + html.Event mouseDown({double clientX, double clientY, int button, int buttons}) { return _downWithFullDetails( pointer: 1, buttons: buttons, @@ -1475,7 +1551,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i return _moveWithFullDetails( pointer: pointer, buttons: 1, - button: unchangedButton, + button: _kNoButtonChange, clientX: clientX, clientY: clientY, pointerType: 'touch', @@ -1483,7 +1559,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i } @override - html.Event moveWithButton({double clientX, double clientY, int button, int buttons}) { + html.Event mouseMove({double clientX, double clientY, int button, int buttons}) { return _moveWithFullDetails( pointer: 1, buttons: buttons, @@ -1517,7 +1593,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i } @override - html.Event upWithButton({double clientX, double clientY, int button}) { + html.Event mouseUp({double clientX, double clientY, int button}) { return _upWithFullDetails( pointer: 1, button: button, From 4744b4b374f70f5bc09ed4502e4b4ac53f5eca6e Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 16:43:14 -0800 Subject: [PATCH 22/28] Fix mouse adapter --- .../lib/src/engine/pointer_binding.dart | 33 +++++++++++++++++-- .../test/engine/pointer_binding_test.dart | 26 +-------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 93907144ad927..d27e7d047467a 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -20,7 +20,26 @@ const int _kButtonsMask = 0x3FFFFFFF; // Intentionally set to -1 so it doesn't conflict with other device IDs. const int _mouseDeviceId = -1; -const int _kPrimaryMouseButton = 1; +const int _kPrimaryMouseButton = 0x1; +const int _kSecondaryMouseButton = 0x2; +const int _kMiddleMouseButton =0x4; + +int _nthButton(int n) => 0x1 << n; + +@visibleForTesting +int convertButtonToButtons(int button) { + assert(button >= 0, 'Unexpected negative button $button.'); + switch(button) { + case 0: + return _kPrimaryMouseButton; + case 1: + return _kMiddleMouseButton; + case 2: + return _kSecondaryMouseButton; + default: + return _nthButton(button); + } +} class PointerBinding { /// The singleton instance of this object. @@ -652,7 +671,11 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { void setup() { _addMouseEventListener('mousedown', (html.MouseEvent event) { final List pointerData = []; - for (_SanitizedDetails details in _sanitizer.sanitizeDownEvent(buttons: event.buttons)) { + final bool isStartOfDrag = event.buttons == convertButtonToButtons(event.button); + final List<_SanitizedDetails> sanitizedDetails = isStartOfDrag ? + _sanitizer.sanitizeDownEvent(buttons: event.buttons) : + _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + for (_SanitizedDetails details in sanitizedDetails) { _convertEventToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); @@ -668,7 +691,11 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addMouseEventListener('mouseup', (html.MouseEvent event) { final List pointerData = []; - for (_SanitizedDetails details in _sanitizer.sanitizeUpEvent()) { + final bool isEndOfDrag = event.buttons == 0; + final List<_SanitizedDetails> sanitizedDetails = isEndOfDrag ? + _sanitizer.sanitizeUpEvent() : + _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + for (_SanitizedDetails details in sanitizedDetails) { _convertEventToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 65c10bb473bfa..e2deb20d891b5 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -10,12 +10,6 @@ import 'package:ui/ui.dart' as ui; import 'package:test/test.dart'; -const int _kPrimaryMouseButton = 0x1; -const int _kSecondaryMouseButton = 0x2; -const int _kMiddleMouseButton =0x4; -const int _kBackwardMouseButton = 0x8; -const int _kForwardMouseButton = 0x10; - const int _kNoButtonChange = -1; void main() { @@ -1420,24 +1414,6 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp @override bool get hasMouseEvents => true; - static int _convertButtonToButtons(int button) { - switch(button) { - case 0: - return _kPrimaryMouseButton; - case 1: - return _kSecondaryMouseButton; - case 2: - return _kMiddleMouseButton; - case 3: - return _kBackwardMouseButton; - case 4: - return _kForwardMouseButton; - default: - assert(false, 'Unexpected button $button.'); - return -1; - } - } - @override html.Event mouseDown({double clientX, double clientY, int button, int buttons}) { return _createMouseEvent( @@ -1452,7 +1428,7 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp @override html.Event mouseMove({double clientX, double clientY, int button, int buttons}) { final bool hasButtonChange = button != _kNoButtonChange; - final bool changeIsButtonDown = hasButtonChange && (buttons & _convertButtonToButtons(button)) != 0; + final bool changeIsButtonDown = hasButtonChange && (buttons & convertButtonToButtons(button)) != 0; final String adjustedType = !hasButtonChange ? 'mousemove' : changeIsButtonDown ? 'mousedown' : 'mouseup'; From 447964abfab64491b723ebb4bdc1ca8deeef0aee Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 16:58:27 -0800 Subject: [PATCH 23/28] Add doc --- .../lib/src/engine/pointer_binding.dart | 29 +++++++++++++++++++ .../test/engine/pointer_binding_test.dart | 10 +++++++ 2 files changed, 39 insertions(+) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index d27e7d047467a..9a789673f7f6f 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -26,6 +26,16 @@ const int _kMiddleMouseButton =0x4; int _nthButton(int n) => 0x1 << n; +/// Convert the `button` property of PointerEvent or MouseEvent to a bit mask of +/// its `buttons` property. +/// +/// The `button` property is a integer describing the button changed in an event, +/// which is sequentially 0 for LMB, 1 for MMB, 2 for RMB, 3 for backward and +/// 4 for forward, etc. +/// +/// The `buttons` property is a bitfield describing the buttons pressed after an +/// event, which is 0x1 for LMB, 0x4 for MMB, 0x2 for RMB, 0x8 for backward +/// and 0x10 for forward, etc. @visibleForTesting int convertButtonToButtons(int button) { assert(button >= 0, 'Unexpected negative button $button.'); @@ -340,6 +350,8 @@ class _ButtonSanitizer { typedef _PointerEventListener = dynamic Function(html.PointerEvent event); /// Adapter class to be used with browsers that support native pointer events. +/// +/// For the difference between MouseEvent and PointerEvent, see _MouseAdapter. class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _PointerAdapter( _PointerDataCallback callback, @@ -651,6 +663,23 @@ class _TouchAdapter extends _BaseAdapter { typedef _MouseEventListener = dynamic Function(html.MouseEvent event); /// Adapter to be used with browsers that support mouse events. +/// +/// The difference between MouseEvent and PointerEvent can be illustrated using +/// a scenario of changing buttons during a drag sequence: LMB down, RMB down, +/// move, LMB up, RMB up, hover. +/// +/// LMB down RMB down move LMB up RMB up hover +/// PntEvt type | pointerdown pointermove pointermove pointermove pointerup pointermove +/// button | 0 2 -1 0 2 -1 +/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0 +/// MosEvt type | mousedown mousedown mousemove mouseup mouseup mousemove +/// button | 0 2 0 0 2 0 +/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0 +/// +/// The major differences are: +/// +/// * The type of events for changing buttons during a drag sequence. +/// * The `button` for draging or hovering. class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _MouseAdapter( _PointerDataCallback callback, diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index e2deb20d891b5..5e2fe64f87881 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -1325,6 +1325,8 @@ mixin _MultiPointerEventMixin on _BasicEventContext { } } +// A test context for `_TouchAdapter`, including its name, PointerSupportDetector +// to override, and how to generate events. class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin implements PointerSupportDetector { _TouchEventContext() { _target = html.document.createElement('div'); @@ -1401,6 +1403,10 @@ class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin } } +// A test context for `_MouseAdapter`, including its name, PointerSupportDetector +// to override, and how to generate events. +// +// For the difference between MouseEvent and PointerEvent, see _MouseAdapter. class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin implements PointerSupportDetector { @override String get name => 'MouseAdapter'; @@ -1474,6 +1480,10 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp } } +// A test context for `_PointerAdapter`, including its name, PointerSupportDetector +// to override, and how to generate events. +// +// For the difference between MouseEvent and PointerEvent, see _MouseAdapter. class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin implements PointerSupportDetector, _MultiPointerEventMixin { @override String get name => 'PointerAdapter'; From 3696becd05af0de273af84b8f2941daf00520d77 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 17:41:21 -0800 Subject: [PATCH 24/28] Fix multitouch tests --- .../test/engine/pointer_binding_test.dart | 485 ++++++++++-------- 1 file changed, 271 insertions(+), 214 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 5e2fe64f87881..d4c88aaa0c7b4 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -12,6 +12,10 @@ import 'package:test/test.dart'; const int _kNoButtonChange = -1; +List _allPointerData(List packets) { + return packets.expand((ui.PointerDataPacket packet) => packet.data).toList(); +} + void main() { html.Element glassPane = domRenderer.glassPaneElement; @@ -27,9 +31,13 @@ void main() { expect(e.runtimeType, equals(html.PointerEvent)); return e; } + List expectCorrectTypes(List events) { + return events.map(expectCorrectType).toList(); + } final _PointerEventContext context = _PointerEventContext(); html.PointerEvent event; + List events; event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101)); expect(event.type, equals('pointerdown')); @@ -47,13 +55,23 @@ void main() { expect(event.client.x, equals(110)); expect(event.client.y, equals(111)); - event = expectCorrectType(context.downWithPointer(clientX: 120, clientY: 121, pointer: 100)); - expect(event.type, equals('pointerdown')); - expect(event.pointerId, equals(100)); - expect(event.button, equals(0)); - expect(event.buttons, equals(1)); - expect(event.client.x, equals(120)); - expect(event.client.y, equals(121)); + events = expectCorrectTypes(context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 100, clientX: 120, clientY: 121), + _TouchDetails(pointer: 101, clientX: 122, clientY: 123), + ])); + expect(events.length, equals(2)); + expect(events[0].type, equals('pointerdown')); + expect(events[0].pointerId, equals(100)); + expect(events[0].button, equals(0)); + expect(events[0].buttons, equals(1)); + expect(events[0].client.x, equals(120)); + expect(events[0].client.y, equals(121)); + expect(events[1].type, equals('pointerdown')); + expect(events[1].pointerId, equals(101)); + expect(events[1].button, equals(0)); + expect(events[1].buttons, equals(1)); + expect(events[1].client.x, equals(122)); + expect(events[1].client.y, equals(123)); event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201)); expect(event.type, equals('pointermove')); @@ -87,13 +105,23 @@ void main() { expect(event.client.x, equals(214)); expect(event.client.y, equals(215)); - event = expectCorrectType(context.moveWithPointer(clientX: 220, clientY: 221, pointer: 101)); - expect(event.type, equals('pointermove')); - expect(event.pointerId, equals(101)); - expect(event.button, equals(-1)); - expect(event.buttons, equals(1)); - expect(event.client.x, equals(220)); - expect(event.client.y, equals(221)); + events = expectCorrectTypes(context.multiTouchMove(<_TouchDetails>[ + _TouchDetails(pointer: 102, clientX: 220, clientY: 221), + _TouchDetails(pointer: 103, clientX: 222, clientY: 223), + ])); + expect(events.length, equals(2)); + expect(events[0].type, equals('pointermove')); + expect(events[0].pointerId, equals(102)); + expect(events[0].button, equals(-1)); + expect(events[0].buttons, equals(1)); + expect(events[0].client.x, equals(220)); + expect(events[0].client.y, equals(221)); + expect(events[1].type, equals('pointermove')); + expect(events[1].pointerId, equals(103)); + expect(events[1].button, equals(-1)); + expect(events[1].buttons, equals(1)); + expect(events[1].client.x, equals(222)); + expect(events[1].client.y, equals(223)); event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301)); expect(event.type, equals('pointerup')); @@ -111,13 +139,23 @@ void main() { expect(event.client.x, equals(310)); expect(event.client.y, equals(311)); - event = expectCorrectType(context.upWithPointer(clientX: 320, clientY: 321, pointer: 102)); - expect(event.type, equals('pointerup')); - expect(event.pointerId, equals(102)); - expect(event.button, equals(0)); - expect(event.buttons, equals(0)); - expect(event.client.x, equals(320)); - expect(event.client.y, equals(321)); + events = expectCorrectTypes(context.multiTouchUp(<_TouchDetails>[ + _TouchDetails(pointer: 104, clientX: 320, clientY: 321), + _TouchDetails(pointer: 105, clientX: 322, clientY: 323), + ])); + expect(events.length, equals(2)); + expect(events[0].type, equals('pointerup')); + expect(events[0].pointerId, equals(104)); + expect(events[0].button, equals(0)); + expect(events[0].buttons, equals(0)); + expect(events[0].client.x, equals(320)); + expect(events[0].client.y, equals(321)); + expect(events[1].type, equals('pointerup')); + expect(events[1].pointerId, equals(105)); + expect(events[1].button, equals(0)); + expect(events[1].buttons, equals(0)); + expect(events[1].client.x, equals(322)); + expect(events[1].client.y, equals(323)); event = expectCorrectType(context.hover(clientX: 400, clientY: 401)); expect(event.type, equals('pointermove')); @@ -133,9 +171,13 @@ void main() { expect(e.runtimeType, equals(html.TouchEvent)); return e; } + List expectCorrectTypes(List events) { + return events.map(expectCorrectType).toList(); + } final _TouchEventContext context = _TouchEventContext(); html.TouchEvent event; + List events; event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101)); expect(event.type, equals('touchstart')); @@ -144,12 +186,19 @@ void main() { expect(event.changedTouches[0].client.x, equals(100)); expect(event.changedTouches[0].client.y, equals(101)); - event = expectCorrectType(context.downWithPointer(clientX: 110, clientY: 111, pointer: 100)); - expect(event.type, equals('touchstart')); - expect(event.changedTouches.length, equals(1)); - expect(event.changedTouches[0].identifier, equals(100)); - expect(event.changedTouches[0].client.x, equals(110)); - expect(event.changedTouches[0].client.y, equals(111)); + events = expectCorrectTypes(context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 100, clientX: 120, clientY: 121), + _TouchDetails(pointer: 101, clientX: 122, clientY: 123), + ])); + expect(events.length, equals(1)); + expect(events[0].type, equals('touchstart')); + expect(events[0].changedTouches.length, equals(2)); + expect(events[0].changedTouches[0].identifier, equals(100)); + expect(events[0].changedTouches[0].client.x, equals(120)); + expect(events[0].changedTouches[0].client.y, equals(121)); + expect(events[0].changedTouches[1].identifier, equals(101)); + expect(events[0].changedTouches[1].client.x, equals(122)); + expect(events[0].changedTouches[1].client.y, equals(123)); event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201)); expect(event.type, equals('touchmove')); @@ -158,12 +207,19 @@ void main() { expect(event.changedTouches[0].client.x, equals(200)); expect(event.changedTouches[0].client.y, equals(201)); - event = expectCorrectType(context.moveWithPointer(clientX: 210, clientY: 211, pointer: 101)); - expect(event.type, equals('touchmove')); - expect(event.changedTouches.length, equals(1)); - expect(event.changedTouches[0].identifier, equals(101)); - expect(event.changedTouches[0].client.x, equals(210)); - expect(event.changedTouches[0].client.y, equals(211)); + events = expectCorrectTypes(context.multiTouchMove(<_TouchDetails>[ + _TouchDetails(pointer: 102, clientX: 220, clientY: 221), + _TouchDetails(pointer: 103, clientX: 222, clientY: 223), + ])); + expect(events.length, equals(1)); + expect(events[0].type, equals('touchmove')); + expect(events[0].changedTouches.length, equals(2)); + expect(events[0].changedTouches[0].identifier, equals(102)); + expect(events[0].changedTouches[0].client.x, equals(220)); + expect(events[0].changedTouches[0].client.y, equals(221)); + expect(events[0].changedTouches[1].identifier, equals(103)); + expect(events[0].changedTouches[1].client.x, equals(222)); + expect(events[0].changedTouches[1].client.y, equals(223)); event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301)); expect(event.type, equals('touchend')); @@ -172,12 +228,19 @@ void main() { expect(event.changedTouches[0].client.x, equals(300)); expect(event.changedTouches[0].client.y, equals(301)); - event = expectCorrectType(context.upWithPointer(clientX: 310, clientY: 311, pointer: 102)); - expect(event.type, equals('touchend')); - expect(event.changedTouches.length, equals(1)); - expect(event.changedTouches[0].identifier, equals(102)); - expect(event.changedTouches[0].client.x, equals(310)); - expect(event.changedTouches[0].client.y, equals(311)); + events = expectCorrectTypes(context.multiTouchUp(<_TouchDetails>[ + _TouchDetails(pointer: 104, clientX: 320, clientY: 321), + _TouchDetails(pointer: 105, clientX: 322, clientY: 323), + ])); + expect(events.length, equals(1)); + expect(events[0].type, equals('touchend')); + expect(events[0].changedTouches.length, equals(2)); + expect(events[0].changedTouches[0].identifier, equals(104)); + expect(events[0].changedTouches[0].client.x, equals(320)); + expect(events[0].changedTouches[0].client.y, equals(321)); + expect(events[0].changedTouches[1].identifier, equals(105)); + expect(events[0].changedTouches[1].client.x, equals(322)); + expect(events[0].changedTouches[1].client.y, equals(323)); }); test('_MouseEventContext generates expected events', () { @@ -946,90 +1009,94 @@ void main() { test('${context.name} treats each pointer separately', () { PointerBinding.instance.debugOverrideDetector(context); List packets = []; + List data; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; - glassPane.dispatchEvent(context.downWithPointer( - pointer: 2, - clientX: 100, - clientY: 101, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].device, equals(2)); - expect(packets[0].data[0].physicalX, equals(100)); - expect(packets[0].data[0].physicalY, equals(101)); - - expect(packets[0].data[1].device, equals(2)); - expect(packets[0].data[1].buttons, equals(1)); - expect(packets[0].data[1].physicalX, equals(100)); - expect(packets[0].data[1].physicalY, equals(101)); - expect(packets[0].data[1].physicalDeltaX, equals(0)); - expect(packets[0].data[1].physicalDeltaY, equals(0)); - packets.clear(); - - glassPane.dispatchEvent(context.downWithPointer( - pointer: 3, - clientX: 200, - clientY: 201, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.add)); - expect(packets[0].data[0].synthesized, equals(true)); - expect(packets[0].data[0].device, equals(3)); - expect(packets[0].data[0].physicalX, equals(200)); - expect(packets[0].data[0].physicalY, equals(201)); + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + _TouchDetails(pointer: 3, clientX: 200, clientY: 201), + ]).forEach(glassPane.dispatchEvent); + if (context.runtimeType == _PointerEventContext) { + expect(packets.length, 2); + expect(packets[0].data.length, 2); + expect(packets[1].data.length, 2); + } else if (context.runtimeType == _TouchEventContext) { + expect(packets.length, 1); + expect(packets[0].data.length, 4); + } else { + assert(false, 'Unexpected context type ${context.runtimeType}'); + } - expect(packets[0].data[1].change, equals(ui.PointerChange.down)); - expect(packets[0].data[1].device, equals(3)); - expect(packets[0].data[1].buttons, equals(1)); - expect(packets[0].data[1].physicalX, equals(200)); - expect(packets[0].data[1].physicalY, equals(201)); - expect(packets[0].data[1].physicalDeltaX, equals(0)); - expect(packets[0].data[1].physicalDeltaY, equals(0)); + data = _allPointerData(packets); + expect(data, hasLength(4)); + expect(data[0].change, equals(ui.PointerChange.add)); + expect(data[0].synthesized, equals(true)); + expect(data[0].device, equals(2)); + expect(data[0].physicalX, equals(100)); + expect(data[0].physicalY, equals(101)); + + expect(data[1].change, equals(ui.PointerChange.down)); + expect(data[1].device, equals(2)); + expect(data[1].buttons, equals(1)); + expect(data[1].physicalX, equals(100)); + expect(data[1].physicalY, equals(101)); + expect(data[1].physicalDeltaX, equals(0)); + expect(data[1].physicalDeltaY, equals(0)); + + expect(data[2].change, equals(ui.PointerChange.add)); + expect(data[2].synthesized, equals(true)); + expect(data[2].device, equals(3)); + expect(data[2].physicalX, equals(200)); + expect(data[2].physicalY, equals(201)); + + expect(data[3].change, equals(ui.PointerChange.down)); + expect(data[3].device, equals(3)); + expect(data[3].buttons, equals(1)); + expect(data[3].physicalX, equals(200)); + expect(data[3].physicalY, equals(201)); + expect(data[3].physicalDeltaX, equals(0)); + expect(data[3].physicalDeltaY, equals(0)); packets.clear(); - glassPane.dispatchEvent(context.moveWithPointer( - pointer: 3, - clientX: 300, - clientY: 302, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].device, equals(3)); - expect(packets[0].data[0].buttons, equals(1)); - expect(packets[0].data[0].physicalX, equals(300)); - expect(packets[0].data[0].physicalY, equals(302)); - expect(packets[0].data[0].physicalDeltaX, equals(100)); - expect(packets[0].data[0].physicalDeltaY, equals(101)); - packets.clear(); + context.multiTouchMove(<_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 300, clientY: 302), + _TouchDetails(pointer: 2, clientX: 400, clientY: 402), + ]).forEach(glassPane.dispatchEvent); + if (context.runtimeType == _PointerEventContext) { + expect(packets.length, 2); + expect(packets[0].data.length, 1); + expect(packets[1].data.length, 1); + } else if (context.runtimeType == _TouchEventContext) { + expect(packets.length, 1); + expect(packets[0].data.length, 2); + } else { + assert(false, 'Unexpected context type ${context.runtimeType}'); + } - glassPane.dispatchEvent(context.moveWithPointer( - pointer: 2, - clientX: 400, - clientY: 402, - )); - expect(packets, hasLength(1)); - expect(packets[0].data, hasLength(1)); - expect(packets[0].data[0].change, equals(ui.PointerChange.move)); - expect(packets[0].data[0].device, equals(2)); - expect(packets[0].data[0].buttons, equals(1)); - expect(packets[0].data[0].physicalX, equals(400)); - expect(packets[0].data[0].physicalY, equals(402)); - expect(packets[0].data[0].physicalDeltaX, equals(300)); - expect(packets[0].data[0].physicalDeltaY, equals(301)); + data = _allPointerData(packets); + expect(data, hasLength(2)); + expect(data[0].change, equals(ui.PointerChange.move)); + expect(data[0].device, equals(3)); + expect(data[0].buttons, equals(1)); + expect(data[0].physicalX, equals(300)); + expect(data[0].physicalY, equals(302)); + expect(data[0].physicalDeltaX, equals(100)); + expect(data[0].physicalDeltaY, equals(101)); + + expect(data[1].change, equals(ui.PointerChange.move)); + expect(data[1].device, equals(2)); + expect(data[1].buttons, equals(1)); + expect(data[1].physicalX, equals(400)); + expect(data[1].physicalY, equals(402)); + expect(data[1].physicalDeltaX, equals(300)); + expect(data[1].physicalDeltaY, equals(301)); packets.clear(); - glassPane.dispatchEvent(context.upWithPointer( - pointer: 3, - clientX: 300, - clientY: 302, - )); + context.multiTouchUp(<_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 300, clientY: 302), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -1049,11 +1116,9 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); - glassPane.dispatchEvent(context.upWithPointer( - pointer: 2, - clientX: 400, - clientY: 402, - )); + context.multiTouchUp(<_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 400, clientY: 402), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -1085,9 +1150,9 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.downWithPointer( - pointer: 1, - )); + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 1, clientX: 100, clientY: 101), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); // An add will be synthesized. expect(packets[0].data, hasLength(2)); @@ -1098,9 +1163,9 @@ void main() { expect(packets[0].data[1].device, equals(1)); packets.clear(); - glassPane.dispatchEvent(context.downWithPointer( - pointer: 2, - )); + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 200, clientY: 202), + ]).forEach(glassPane.dispatchEvent); // An add will be synthesized. expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -1124,11 +1189,9 @@ void main() { packets.add(packet); }; - glassPane.dispatchEvent(context.downWithPointer( - pointer: 1, - clientX: 20.0, - clientY: 20.0, - )); + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 1, clientX: 20, clientY: 20), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -1148,11 +1211,9 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.moveWithPointer( - pointer: 1, - clientX: 40.0, - clientY: 30.0, - )); + context.multiTouchMove(<_TouchDetails>[ + _TouchDetails(pointer: 1, clientX: 40, clientY: 30), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -1164,11 +1225,9 @@ void main() { expect(packets[0].data[0].physicalDeltaY, equals(10.0)); packets.clear(); - glassPane.dispatchEvent(context.upWithPointer( - pointer: 1, - clientX: 40.0, - clientY: 30.0, - )); + context.multiTouchUp(<_TouchDetails>[ + _TouchDetails(pointer: 1, clientX: 40, clientY: 30), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -1188,11 +1247,9 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.downWithPointer( - pointer: 2, - clientX: 20.0, - clientY: 10.0, - )); + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 20, clientY: 10), + ]).forEach(glassPane.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -1292,36 +1349,50 @@ mixin _ButtonedEventMixin on _BasicEventContext { } } +class _TouchDetails { + const _TouchDetails({this.pointer, this.clientX, this.clientY}); + + final int pointer; + final double clientX; + final double clientY; +} + mixin _MultiPointerEventMixin on _BasicEventContext { - html.Event downWithPointer({double clientX, double clientY, int pointer}); - html.Event moveWithPointer({double clientX, double clientY, int pointer}); - html.Event upWithPointer({double clientX, double clientY, int pointer}); + List multiTouchDown(List<_TouchDetails> touches); + List multiTouchMove(List<_TouchDetails> touches); + List multiTouchUp(List<_TouchDetails> touches); @override html.Event primaryDown({double clientX, double clientY}) { - return downWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); + return multiTouchDown(<_TouchDetails>[ + _TouchDetails( + pointer: 1, + clientX: clientX, + clientY: clientY, + ), + ])[0]; } @override html.Event primaryMove({double clientX, double clientY}) { - return moveWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); + return multiTouchMove(<_TouchDetails>[ + _TouchDetails( + pointer: 1, + clientX: clientX, + clientY: clientY, + ), + ])[0]; } @override html.Event primaryUp({double clientX, double clientY}) { - return upWithPointer( - pointer: 1, - clientX: clientX, - clientY: clientY, - ); + return multiTouchUp(<_TouchDetails>[ + _TouchDetails( + pointer: 1, + clientX: clientX, + clientY: clientY, + ), + ])[0]; } } @@ -1359,47 +1430,33 @@ class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin }); } - html.TouchEvent _createTouchEvent(String eventType, { - int identifier, - double clientX, - double clientY, - }) { + html.TouchEvent _createTouchEvent(String eventType, List<_TouchDetails> touches) { return html.TouchEvent(eventType, { - 'changedTouches': [ - _createTouch( - identifier: identifier, - clientX: clientX, - clientY: clientY, - ) - ], - }); + 'changedTouches': touches.map( + (_TouchDetails details) => + _createTouch( + identifier: details.pointer, + clientX: details.clientX, + clientY: details.clientY, + ), + ).toList(), + }, + ); } - html.Event downWithPointer({double clientX, double clientY, int pointer}) { - return _createTouchEvent( - 'touchstart', - identifier: pointer, - clientX: clientX, - clientY: clientY, - ); + @override + List multiTouchDown(List<_TouchDetails> touches) { + return [_createTouchEvent('touchstart', touches)]; } - html.Event moveWithPointer({double clientX, double clientY, int pointer}) { - return _createTouchEvent( - 'touchmove', - identifier: pointer, - clientX: clientX, - clientY: clientY, - ); + @override + List multiTouchMove(List<_TouchDetails> touches) { + return [_createTouchEvent('touchmove', touches)]; } - html.Event upWithPointer({double clientX, double clientY, int pointer}) { - return _createTouchEvent( - 'touchend', - identifier: pointer, - clientX: clientX, - clientY: clientY, - ); + @override + List multiTouchUp(List<_TouchDetails> touches) { + return [_createTouchEvent('touchend', touches)]; } } @@ -1498,15 +1555,15 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i bool get hasMouseEvents => false; @override - html.Event downWithPointer({double clientX, double clientY, int pointer}) { - return _downWithFullDetails( - pointer: pointer, + List multiTouchDown(List<_TouchDetails> touches) { + return touches.map((_TouchDetails details) => _downWithFullDetails( + pointer: details.pointer, buttons: 1, button: 0, - clientX: clientX, - clientY: clientY, + clientX: details.clientX, + clientY: details.clientY, pointerType: 'touch', - ); + )).toList(); } @override @@ -1533,15 +1590,15 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i } @override - html.Event moveWithPointer({double clientX, double clientY, int pointer}) { - return _moveWithFullDetails( - pointer: pointer, + List multiTouchMove(List<_TouchDetails> touches) { + return touches.map((_TouchDetails details) => _moveWithFullDetails( + pointer: details.pointer, buttons: 1, button: _kNoButtonChange, - clientX: clientX, - clientY: clientY, + clientX: details.clientX, + clientY: details.clientY, pointerType: 'touch', - ); + )).toList(); } @override @@ -1568,14 +1625,14 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i } @override - html.Event upWithPointer({double clientX, double clientY, int pointer}) { - return _upWithFullDetails( - pointer: pointer, + List multiTouchUp(List<_TouchDetails> touches) { + return touches.map((_TouchDetails details) => _upWithFullDetails( + pointer: details.pointer, button: 0, - clientX: clientX, - clientY: clientY, + clientX: details.clientX, + clientY: details.clientY, pointerType: 'touch', - ); + )).toList(); } @override From 447464fb701faad7b0b717feb051ca7226c930df Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 17 Dec 2019 18:57:36 -0800 Subject: [PATCH 25/28] Add cancel --- .../lib/src/engine/pointer_binding.dart | 3 +- .../lib/src/engine/pointer_converter.dart | 14 +- .../test/engine/pointer_binding_test.dart | 142 ++++++++++++++++++ 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 9a789673f7f6f..64420623c036f 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -432,7 +432,6 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) - // TODO(dkwingsmt): Add tests for cancel _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; @@ -609,7 +608,6 @@ class _TouchAdapter extends _BaseAdapter { _callback(pointerData); }); - // TODO(dkwingsmt): Add tests for cancel _addTouchEventListener('touchcancel', (html.TouchEvent event) { final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); final List pointerData = []; @@ -633,6 +631,7 @@ class _TouchAdapter extends _BaseAdapter { ); } } + _callback(pointerData); }); } diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index c0cac2d717a58..6bd4e7c8aaeb9 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -433,6 +433,15 @@ class PointerDataConverter { assert(_pointers.containsKey(device)); final _PointerState state = _pointers[device]; assert(state.down); + // Cancel events can have different coordinates due to various + // reasons (window lost focus which is accompanied by window + // movement, or PointerEvent simply always gives 0). Instead of + // caring about the coordinates, we want to cancel the pointers as + // soon as possible. + if (change == ui.PointerChange.cancel) { + physicalX = state.x; + physicalY = state.y; + } assert(!_locationHasChanged(device, physicalX, physicalY)); state.down = false; result.add( @@ -468,7 +477,6 @@ class PointerDataConverter { assert(_pointers.containsKey(device)); final _PointerState state = _pointers[device]; assert(!state.down); - assert(!_locationHasChanged(device, physicalX, physicalY)); result.add( _generateCompletePointerData( timeStamp: timeStamp, @@ -476,8 +484,8 @@ class PointerDataConverter { kind: kind, signalKind: signalKind, device: device, - physicalX: physicalX, - physicalY: physicalY, + physicalX: state.x, + physicalY: state.y, buttons: buttons, obscured: obscured, pressure: pressure, diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index d4c88aaa0c7b4..d5743997b5675 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -164,6 +164,24 @@ void main() { expect(event.buttons, equals(0)); expect(event.client.x, equals(400)); expect(event.client.y, equals(401)); + + events = expectCorrectTypes(context.multiTouchCancel(<_TouchDetails>[ + _TouchDetails(pointer: 106, clientX: 500, clientY: 501), + _TouchDetails(pointer: 107, clientX: 502, clientY: 503), + ])); + expect(events.length, equals(2)); + expect(events[0].type, equals('pointercancel')); + expect(events[0].pointerId, equals(106)); + expect(events[0].button, equals(0)); + expect(events[0].buttons, equals(0)); + expect(events[0].client.x, equals(0)); + expect(events[0].client.y, equals(0)); + expect(events[1].type, equals('pointercancel')); + expect(events[1].pointerId, equals(107)); + expect(events[1].button, equals(0)); + expect(events[1].buttons, equals(0)); + expect(events[1].client.x, equals(0)); + expect(events[1].client.y, equals(0)); }); test('_TouchEventContext generates expected events', () { @@ -241,6 +259,20 @@ void main() { expect(events[0].changedTouches[1].identifier, equals(105)); expect(events[0].changedTouches[1].client.x, equals(322)); expect(events[0].changedTouches[1].client.y, equals(323)); + + events = expectCorrectTypes(context.multiTouchCancel(<_TouchDetails>[ + _TouchDetails(pointer: 104, clientX: 320, clientY: 321), + _TouchDetails(pointer: 105, clientX: 322, clientY: 323), + ])); + expect(events.length, equals(1)); + expect(events[0].type, equals('touchcancel')); + expect(events[0].changedTouches.length, equals(2)); + expect(events[0].changedTouches[0].identifier, equals(104)); + expect(events[0].changedTouches[0].client.x, equals(320)); + expect(events[0].changedTouches[0].client.y, equals(321)); + expect(events[0].changedTouches[1].identifier, equals(105)); + expect(events[0].changedTouches[1].client.x, equals(322)); + expect(events[0].changedTouches[1].client.y, equals(323)); }); test('_MouseEventContext generates expected events', () { @@ -1014,6 +1046,7 @@ void main() { packets.add(packet); }; + // Two pointers down context.multiTouchDown(<_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 100, clientY: 101), _TouchDetails(pointer: 3, clientX: 200, clientY: 201), @@ -1060,6 +1093,7 @@ void main() { expect(data[3].physicalDeltaY, equals(0)); packets.clear(); + // Two pointers move context.multiTouchMove(<_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 300, clientY: 302), _TouchDetails(pointer: 2, clientX: 400, clientY: 402), @@ -1094,6 +1128,7 @@ void main() { expect(data[1].physicalDeltaY, equals(301)); packets.clear(); + // One pointer up context.multiTouchUp(<_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 300, clientY: 302), ]).forEach(glassPane.dispatchEvent); @@ -1116,6 +1151,7 @@ void main() { expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); + // Another pointer up context.multiTouchUp(<_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 400, clientY: 402), ]).forEach(glassPane.dispatchEvent); @@ -1137,6 +1173,94 @@ void main() { expect(packets[0].data[1].physicalDeltaX, equals(0)); expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); + + // Again two pointers down (reuse pointer ID) + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 500, clientY: 501), + _TouchDetails(pointer: 2, clientX: 600, clientY: 601), + ]).forEach(glassPane.dispatchEvent); + if (context.runtimeType == _PointerEventContext) { + expect(packets.length, 2); + expect(packets[0].data.length, 2); + expect(packets[1].data.length, 2); + } else if (context.runtimeType == _TouchEventContext) { + expect(packets.length, 1); + expect(packets[0].data.length, 4); + } else { + assert(false, 'Unexpected context type ${context.runtimeType}'); + } + + data = _allPointerData(packets); + expect(data, hasLength(4)); + expect(data[0].change, equals(ui.PointerChange.add)); + expect(data[0].synthesized, equals(true)); + expect(data[0].device, equals(3)); + expect(data[0].physicalX, equals(500)); + expect(data[0].physicalY, equals(501)); + + expect(data[1].change, equals(ui.PointerChange.down)); + expect(data[1].device, equals(3)); + expect(data[1].buttons, equals(1)); + expect(data[1].physicalX, equals(500)); + expect(data[1].physicalY, equals(501)); + expect(data[1].physicalDeltaX, equals(0)); + expect(data[1].physicalDeltaY, equals(0)); + + expect(data[2].change, equals(ui.PointerChange.add)); + expect(data[2].synthesized, equals(true)); + expect(data[2].device, equals(2)); + expect(data[2].physicalX, equals(600)); + expect(data[2].physicalY, equals(601)); + + expect(data[3].change, equals(ui.PointerChange.down)); + expect(data[3].device, equals(2)); + expect(data[3].buttons, equals(1)); + expect(data[3].physicalX, equals(600)); + expect(data[3].physicalY, equals(601)); + expect(data[3].physicalDeltaX, equals(0)); + expect(data[3].physicalDeltaY, equals(0)); + packets.clear(); + }); + }); + + <_MultiPointerEventMixin>[_PointerEventContext(), _TouchEventContext()].forEach((_MultiPointerEventMixin context) { + test('${context.name} correctly parses cancel event', () { + PointerBinding.instance.debugOverrideDetector(context); + List packets = []; + List data; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Two pointers down + context.multiTouchDown(<_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + _TouchDetails(pointer: 3, clientX: 200, clientY: 201), + ]).forEach(glassPane.dispatchEvent); + packets.clear(); // Down event is tested in other tests. + + // One pointer cancel + context.multiTouchCancel(<_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 300, clientY: 302), + ]).forEach(glassPane.dispatchEvent); + expect(packets.length, 1); + expect(packets[0].data.length, 2); + expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); + expect(packets[0].data[0].device, equals(3)); + expect(packets[0].data[0].buttons, equals(0)); + expect(packets[0].data[0].physicalX, equals(200)); + expect(packets[0].data[0].physicalY, equals(201)); + expect(packets[0].data[0].physicalDeltaX, equals(0)); + expect(packets[0].data[0].physicalDeltaY, equals(0)); + + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(3)); + expect(packets[0].data[1].buttons, equals(0)); + expect(packets[0].data[1].physicalX, equals(200)); + expect(packets[0].data[1].physicalY, equals(201)); + expect(packets[0].data[1].physicalDeltaX, equals(0)); + expect(packets[0].data[1].physicalDeltaY, equals(0)); + packets.clear(); }); }); @@ -1361,6 +1485,7 @@ mixin _MultiPointerEventMixin on _BasicEventContext { List multiTouchDown(List<_TouchDetails> touches); List multiTouchMove(List<_TouchDetails> touches); List multiTouchUp(List<_TouchDetails> touches); + List multiTouchCancel(List<_TouchDetails> touches); @override html.Event primaryDown({double clientX, double clientY}) { @@ -1458,6 +1583,11 @@ class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin List multiTouchUp(List<_TouchDetails> touches) { return [_createTouchEvent('touchend', touches)]; } + + @override + List multiTouchCancel(List<_TouchDetails> touches) { + return [_createTouchEvent('touchcancel', touches)]; + } } // A test context for `_MouseAdapter`, including its name, PointerSupportDetector @@ -1656,4 +1786,16 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i 'pointerType': pointerType, }); } + + @override + List multiTouchCancel(List<_TouchDetails> touches) { + return touches.map((_TouchDetails details) => html.PointerEvent('pointercancel', { + 'pointerId': details.pointer, + 'button': 0, + 'buttons': 0, + 'clientX': 0, + 'clientY': 0, + 'pointerType': 'touch', + })).toList(); + } } From e8436ec9b526c390fd27002b6d1b1aefc014865d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 18 Dec 2019 11:35:32 -0800 Subject: [PATCH 26/28] Change to convert by list --- .../lib/src/engine/pointer_binding.dart | 142 +++++++++--------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 64420623c036f..d7fb941d41741 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -374,17 +374,13 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { return sanitizer; } - void _removePointerIfUnhoverable(List pointerData, html.PointerEvent event) { + void _removePointerIfUnhoverable(List<_SanitizedDetails> details, html.PointerEvent event) { if (event.pointerType == 'touch') { _sanitizers.remove(event.pointerId); - _convertEventToPointerData( - data: pointerData, - event: event, - details: _SanitizedDetails( - buttons: 0, - change: ui.PointerChange.remove, - ), - ); + details.add(_SanitizedDetails( + buttons: 0, + change: ui.PointerChange.remove, + )); } } @@ -400,10 +396,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointerdown', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _ButtonSanitizer sanitizer = _ensureSanitizer(device); - for (_SanitizedDetails details in sanitizer.sanitizeDownEvent(buttons: event.buttons)) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } + final List<_SanitizedDetails> detailsList = _ensureSanitizer(device).sanitizeDownEvent(buttons: event.buttons); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); _callback(pointerData); }); @@ -411,22 +405,19 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { final int device = event.pointerId; final _ButtonSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; - for (html.PointerEvent expandedEvent in _expandEvents(event)) { - for (_SanitizedDetails details in sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons)) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } - } + final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).expand( + (html.PointerEvent expandedEvent) => sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons), + ); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); _callback(pointerData); }); _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _ButtonSanitizer sanitizer = _getSanitizer(device); - for (_SanitizedDetails details in sanitizer.sanitizeUpEvent()) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } - _removePointerIfUnhoverable(pointerData, event); + final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeUpEvent(); + _removePointerIfUnhoverable(detailsList, event); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); _callback(pointerData); }); @@ -435,11 +426,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final _ButtonSanitizer sanitizer = _getSanitizer(device); - for (_SanitizedDetails details in sanitizer.sanitizeCancelEvent()) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } - _removePointerIfUnhoverable(pointerData, event); + final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeCancelEvent(); + _removePointerIfUnhoverable(detailsList, event); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); _callback(pointerData); }); @@ -455,31 +444,40 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { }); } - void _convertEventToPointerData({ + // For each event that is de-coalesced from `event` and described in + // `detailsList`, convert it to pointer data and store in `data`. + void _convertEventsToPointerData({ @required List data, @required html.PointerEvent event, - @required _SanitizedDetails details, + @required Iterable<_SanitizedDetails> detailsList, }) { + assert(data != null); + assert(event != null); + assert(detailsList != null); final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType); // We force `device: _mouseDeviceId` on mouse pointers because Wheel events // might come before any PointerEvents, and since wheel events don't contain // pointerId we always assign `device: _mouseDeviceId` to them. final int device = kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : event.pointerId; - _pointerDataConverter.convert( - data, - change: details.change, - timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), - kind: kind, - signalKind: ui.PointerSignalKind.none, - device: device, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: details.buttons, - pressure: event.pressure, - pressureMin: 0.0, - pressureMax: 1.0, - tilt: _computeHighestTilt(event), - ); + final double tilt = _computeHighestTilt(event); + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + for (_SanitizedDetails details in detailsList) { + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: timeStamp, + kind: kind, + signalKind: ui.PointerSignalKind.none, + device: device, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: details.buttons, + pressure: event.pressure, + pressureMin: 0.0, + pressureMax: 1.0, + tilt: tilt, + ); + } } List _expandEvents(html.PointerEvent event) { @@ -678,7 +676,7 @@ typedef _MouseEventListener = dynamic Function(html.MouseEvent event); /// The major differences are: /// /// * The type of events for changing buttons during a drag sequence. -/// * The `button` for draging or hovering. +/// * The `button` for dragging or hovering. class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _MouseAdapter( _PointerDataCallback callback, @@ -703,17 +701,14 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final List<_SanitizedDetails> sanitizedDetails = isStartOfDrag ? _sanitizer.sanitizeDownEvent(buttons: event.buttons) : _sanitizer.sanitizeMoveEvent(buttons: event.buttons); - for (_SanitizedDetails details in sanitizedDetails) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); _callback(pointerData); }); _addMouseEventListener('mousemove', (html.MouseEvent event) { final List pointerData = []; - for (_SanitizedDetails details in _sanitizer.sanitizeMoveEvent(buttons: event.buttons)) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } + final List<_SanitizedDetails> sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); _callback(pointerData); }); @@ -723,9 +718,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final List<_SanitizedDetails> sanitizedDetails = isEndOfDrag ? _sanitizer.sanitizeUpEvent() : _sanitizer.sanitizeMoveEvent(buttons: event.buttons); - for (_SanitizedDetails details in sanitizedDetails) { - _convertEventToPointerData(data: pointerData, event: event, details: details); - } + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); _callback(pointerData); }); @@ -741,26 +734,31 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { }); } - void _convertEventToPointerData({ + // For each event that is de-coalesced from `event` and described in + // `detailsList`, convert it to pointer data and store in `data`. + void _convertEventsToPointerData({ @required List data, @required html.MouseEvent event, - @required _SanitizedDetails details, + @required Iterable<_SanitizedDetails> detailsList, }) { + assert(data != null); assert(event != null); - assert(details != null); - _pointerDataConverter.convert( - data, - change: details.change, - timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.none, - device: _mouseDeviceId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: details.buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - ); + assert(detailsList != null); + for (_SanitizedDetails details in detailsList) { + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), + kind: ui.PointerDeviceKind.mouse, + signalKind: ui.PointerSignalKind.none, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: details.buttons, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + ); + } } } From 0f09c077b85f69e811c275681aaa4a6f30480837 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 18 Dec 2019 12:34:47 -0800 Subject: [PATCH 27/28] Add isSupported --- .../test/engine/pointer_binding_test.dart | 172 ++++++++++++------ 1 file changed, 113 insertions(+), 59 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index d5743997b5675..398e5ab9d3d6d 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -11,11 +11,28 @@ import 'package:ui/ui.dart' as ui; import 'package:test/test.dart'; const int _kNoButtonChange = -1; +const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector(); List _allPointerData(List packets) { return packets.expand((ui.PointerDataPacket packet) => packet.data).toList(); } +typedef _ContextTestBody = void Function(T); + +void _testEach( + Iterable contexts, + String description, + _ContextTestBody body, +) { + for (T context in contexts) { + if (context.supported) { + test('${context.name} $description', () { + body(context); + }); + } + } +} + void main() { html.Element glassPane = domRenderer.glassPaneElement; @@ -350,8 +367,10 @@ void main() { // ALL ADAPTERS - [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()].forEach((_BasicEventContext context) { - test('${context.name} can receive pointer events on the glass pane', () { + _testEach( + [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()], + 'can receive pointer events on the glass pane', + (_BasicEventContext context) { PointerBinding.instance.debugOverrideDetector(context); ui.PointerDataPacket receivedPacket; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -362,11 +381,13 @@ void main() { expect(receivedPacket, isNotNull); expect(receivedPacket.data[0].buttons, equals(1)); - }); - }); + }, + ); - [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()].forEach((_BasicEventContext context) { - test('${context.name} does create an add event if got a pointerdown', () { + _testEach( + [_PointerEventContext(), _MouseEventContext(), _TouchEventContext()], + 'does create an add event if got a pointerdown', + (_BasicEventContext context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -380,13 +401,15 @@ void main() { expect(packets.single.data[0].change, equals(ui.PointerChange.add)); expect(packets.single.data[1].change, equals(ui.PointerChange.down)); - }); - }); + }, + ); // BUTTONED ADAPTERS - <_ButtonedEventMixin>[_MouseEventContext(), _PointerEventContext()].forEach((_ButtonedEventMixin context) { - test('${context.name} creates an add event if the first pointer activity is a hover', () { + _testEach<_ButtonedEventMixin>( + [_MouseEventContext(), _PointerEventContext()], + 'creates an add event if the first pointer activity is a hover', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -401,11 +424,13 @@ void main() { expect(packets.single.data[0].change, equals(ui.PointerChange.add)); expect(packets.single.data[0].synthesized, equals(true)); expect(packets.single.data[1].change, equals(ui.PointerChange.hover)); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('${context.name} synthesizes a pointerup event on two pointerdowns in a row', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'synthesizes a pointerup event on two pointerdowns in a row', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -424,11 +449,13 @@ void main() { expect(packets[0].data[1].change, equals(ui.PointerChange.down)); expect(packets[1].data[0].change, equals(ui.PointerChange.up)); expect(packets[1].data[1].change, equals(ui.PointerChange.down)); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('${context.name} does synthesize add or hover or more for scroll', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'does synthesize add or hover or more for scroll', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -535,12 +562,13 @@ void main() { expect(packets[3].data[1].physicalY, equals(60.0)); expect(packets[3].data[1].physicalDeltaX, equals(0.0)); expect(packets[3].data[1].physicalDeltaY, equals(0.0)); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - // Touch is in another test since this test involves hovering - test('${context.name} does calculate delta and pointer identifier correctly', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'does calculate delta and pointer identifier correctly', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -659,11 +687,13 @@ void main() { expect(packets[0].data[0].physicalDeltaX, equals(0.0)); expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('correctly converts buttons of down, move and up events', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'correctly converts buttons of down, move and up events', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -824,11 +854,13 @@ void main() { expect(packets[0].data[0].physicalY, equals(41)); expect(packets[0].data[0].buttons, equals(0)); packets.clear(); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('correctly handles button changes during a down sequence', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'correctly handles button changes during a down sequence', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -884,11 +916,13 @@ void main() { expect(packets[0].data[0].synthesized, equals(false)); expect(packets[0].data[0].buttons, equals(0)); packets.clear(); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('synthesizes a pointerup event when pointermove comes before the up', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'synthesizes a pointerup event when pointermove comes before the up', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); // This can happen when the user pops up the context menu by right // clicking, then dismisses it with a left click. @@ -964,11 +998,13 @@ void main() { expect(packets[0].data[0].physicalY, equals(21)); expect(packets[0].data[0].buttons, equals(0)); packets.clear(); - }); - }); + }, + ); - <_ButtonedEventMixin>[_PointerEventContext(), _MouseEventContext()].forEach((_ButtonedEventMixin context) { - test('correctly handles uncontinuous button changes during a down sequence', () { + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'correctly handles uncontinuous button changes during a down sequence', + (_ButtonedEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); // This can happen with the following gesture sequence: // @@ -1032,13 +1068,15 @@ void main() { expect(packets[0].data[0].synthesized, equals(false)); expect(packets[0].data[0].buttons, equals(0)); packets.clear(); - }); - }); + }, + ); // MULTIPOINTER ADAPTERS - <_MultiPointerEventMixin>[_PointerEventContext(), _TouchEventContext()].forEach((_MultiPointerEventMixin context) { - test('${context.name} treats each pointer separately', () { + _testEach<_MultiPointerEventMixin>( + [_PointerEventContext(), _TouchEventContext()], + 'treats each pointer separately', + (_MultiPointerEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; List data; @@ -1220,14 +1258,15 @@ void main() { expect(data[3].physicalDeltaX, equals(0)); expect(data[3].physicalDeltaY, equals(0)); packets.clear(); - }); - }); + }, + ); - <_MultiPointerEventMixin>[_PointerEventContext(), _TouchEventContext()].forEach((_MultiPointerEventMixin context) { - test('${context.name} correctly parses cancel event', () { + _testEach<_MultiPointerEventMixin>( + [_PointerEventContext(), _TouchEventContext()], + 'correctly parses cancel event', + (_MultiPointerEventMixin context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; - List data; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); }; @@ -1261,13 +1300,15 @@ void main() { expect(packets[0].data[1].physicalDeltaX, equals(0)); expect(packets[0].data[1].physicalDeltaY, equals(0)); packets.clear(); - }); - }); + }, + ); // POINTER ADAPTER - [_PointerEventContext()].forEach((_PointerEventContext context) { - test('${context.name} does not synthesize pointer up if from different device', () { + _testEach( + [_PointerEventContext()], + 'does not synthesize pointer up if from different device', + (_PointerEventContext context) { PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -1299,14 +1340,16 @@ void main() { expect(packets[0].data[1].change, equals(ui.PointerChange.down)); expect(packets[0].data[1].device, equals(2)); packets.clear(); - }); - }); + }, + ); // TOUCH ADAPTER - [_TouchEventContext()].forEach((_TouchEventContext context) { - // Mouse and Pointer are in another test since these tests can involve hovering - test('${context.name} does calculate delta and pointer identifier correctly', () { + _testEach( + [_TouchEventContext()], + 'does calculate delta and pointer identifier correctly', + (_TouchEventContext context) { + // Mouse and Pointer are in another test since these tests can involve hovering PointerBinding.instance.debugOverrideDetector(context); List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { @@ -1392,13 +1435,15 @@ void main() { expect(packets[0].data[1].physicalDeltaX, equals(0.0)); expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - }); - }); + }, + ); } abstract class _BasicEventContext implements PointerSupportDetector { String get name; + bool get supported; + // Generate an event that is: // // * For mouse, a left click @@ -1531,6 +1576,9 @@ class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin @override String get name => 'TouchAdapter'; + @override + bool get supported => _defaultSupportDetector.hasTouchEvents; + @override bool get hasPointerEvents => false; @@ -1598,6 +1646,9 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp @override String get name => 'MouseAdapter'; + @override + bool get supported => _defaultSupportDetector.hasMouseEvents; + @override bool get hasPointerEvents => false; @@ -1675,6 +1726,9 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i @override String get name => 'PointerAdapter'; + @override + bool get supported => _defaultSupportDetector.hasPointerEvents; + @override bool get hasPointerEvents => true; From 1813efd72139bca29c5bb0d051c4c165270bab0c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 18 Dec 2019 12:52:10 -0800 Subject: [PATCH 28/28] Test support for context tester --- .../test/engine/pointer_binding_test.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 398e5ab9d3d6d..6ba4bed7f1cc8 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -25,7 +25,7 @@ void _testEach( _ContextTestBody body, ) { for (T context in contexts) { - if (context.supported) { + if (context.isSupported) { test('${context.name} $description', () { body(context); }); @@ -44,6 +44,9 @@ void main() { }); test('_PointerEventContext generates expected events', () { + if (!_PointerEventContext().isSupported) + return; + html.PointerEvent expectCorrectType(html.Event e) { expect(e.runtimeType, equals(html.PointerEvent)); return e; @@ -202,6 +205,9 @@ void main() { }); test('_TouchEventContext generates expected events', () { + if (!_TouchEventContext().isSupported) + return; + html.TouchEvent expectCorrectType(html.Event e) { expect(e.runtimeType, equals(html.TouchEvent)); return e; @@ -293,6 +299,9 @@ void main() { }); test('_MouseEventContext generates expected events', () { + if (!_MouseEventContext().isSupported) + return; + html.MouseEvent expectCorrectType(html.Event e) { expect(e.runtimeType, equals(html.MouseEvent)); return e; @@ -1442,7 +1451,7 @@ void main() { abstract class _BasicEventContext implements PointerSupportDetector { String get name; - bool get supported; + bool get isSupported; // Generate an event that is: // @@ -1577,7 +1586,7 @@ class _TouchEventContext extends _BasicEventContext with _MultiPointerEventMixin String get name => 'TouchAdapter'; @override - bool get supported => _defaultSupportDetector.hasTouchEvents; + bool get isSupported => _defaultSupportDetector.hasTouchEvents; @override bool get hasPointerEvents => false; @@ -1647,7 +1656,7 @@ class _MouseEventContext extends _BasicEventContext with _ButtonedEventMixin imp String get name => 'MouseAdapter'; @override - bool get supported => _defaultSupportDetector.hasMouseEvents; + bool get isSupported => _defaultSupportDetector.hasMouseEvents; @override bool get hasPointerEvents => false; @@ -1727,7 +1736,7 @@ class _PointerEventContext extends _BasicEventContext with _ButtonedEventMixin i String get name => 'PointerAdapter'; @override - bool get supported => _defaultSupportDetector.hasPointerEvents; + bool get isSupported => _defaultSupportDetector.hasPointerEvents; @override bool get hasPointerEvents => true;