From cefc3820a3089617039f787685cee2e871d2a66f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 2 Dec 2019 15:08:55 -0800 Subject: [PATCH 1/5] add pointer data santizing in flutter web engine --- lib/web_ui/lib/src/engine.dart | 1 + .../lib/src/engine/pointer_binding.dart | 126 +++------ .../lib/src/engine/pointer_converter.dart | 246 ++++++++++++++++++ 3 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/pointer_converter.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index da577462e95dc..d9f9dca215784 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -62,6 +62,7 @@ part 'engine/picture.dart'; part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; +part 'engine/pointer_converter.dart'; part 'engine/recording_canvas.dart'; part 'engine/render_vertices.dart'; part 'engine/rrect_renderer.dart'; diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index e70b9ad1f92b5..9d564661ca289 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -15,13 +15,6 @@ class PointerBinding { static PointerBinding get instance => _instance; static PointerBinding _instance; - // Set of pointerIds that are added before routing hover and mouse wheel - // events. - // - // The device needs to send a one time PointerChange.add before hover and - // wheel events. - Set _activePointerIds = {}; - PointerBinding(this.domRenderer) { if (_instance == null) { _instance = this; @@ -31,7 +24,6 @@ class PointerBinding { assert(() { registerHotRestartListener(() { _adapter?.clearListeners(); - _activePointerIds.clear(); }); return true; }()); @@ -62,7 +54,6 @@ class PointerBinding { newDetector ??= const PointerSupportDetector(); // When changing the detector, we need to swap the adapter. if (newDetector != _detector) { - _activePointerIds.clear(); _detector = newDetector; _adapter?.clearListeners(); _adapter = _createAdapter(); @@ -83,7 +74,11 @@ class PointerBinding { } void _onPointerData(List data) { - final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data); + final List converted = List.from( + PointerDataConverter.convert(data), + ); + print('from data ${data} to converted ${converted}'); + final ui.PointerDataPacket packet = ui.PointerDataPacket(data: converted); final ui.PointerDataPacketCallback callback = ui.window.onPointerDataPacket; if (callback != null) { callback(packet); @@ -215,8 +210,6 @@ class PointerAdapter extends BaseAdapter { _addEventListener('pointerdown', (html.Event event) { final int pointerButton = _pointerButtonFromHtmlEvent(event); final int device = _deviceFromHtmlEvent(event); - // The pointerdown event will cause an 'add' event on the framework side. - PointerBinding._instance._activePointerIds.add(device); if (_isButtonDown(device, pointerButton)) { // TODO(flutter_web): Remove this temporary fix for right click // on web platform once context guesture is implemented. @@ -239,13 +232,6 @@ class PointerAdapter extends BaseAdapter { ? ui.PointerChange.move : ui.PointerChange.hover, pointerEvent); - _ensureMouseDeviceAdded( - data, - pointerEvent.client.x, - pointerEvent.client.y, - pointerEvent.buttons, - pointerEvent.timeStamp, - pointerEvent.pointerId); _callback(data); }); @@ -455,29 +441,21 @@ class MouseAdapter extends BaseAdapter { ui.PointerChange change, html.MouseEvent event, ) { - final List data = []; - // The mousedown event will cause an 'add' event on the framework side. - if (event.type == 'mousedown') { - PointerBinding._instance._activePointerIds.add(_mouseDeviceId); - } - if (event.type == 'mousemove') { - _ensureMouseDeviceAdded(data, event.client.x, event.client.y, - event.buttons, event.timeStamp, _mouseDeviceId); - } - data.add(ui.PointerData( - change: change, - timeStamp: _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, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - )); - return data; + return [ + ui.PointerData( + change: change, + timeStamp: _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, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + ) + ]; } } @@ -490,34 +468,6 @@ Duration _eventTimeStampToDuration(num milliseconds) { return Duration(milliseconds: ms, microseconds: micro); } -void _ensureMouseDeviceAdded(List data, double clientX, - double clientY, int buttons, double timeStamp, int deviceId) { - if (PointerBinding.instance._activePointerIds.contains(deviceId)) { - return; - } - PointerBinding.instance._activePointerIds.add(deviceId); - // Only send [PointerChange.add] the first time. - data.insert( - 0, - ui.PointerData( - change: ui.PointerChange.add, - timeStamp: _eventTimeStampToDuration(timeStamp), - kind: ui.PointerDeviceKind.mouse, - // In order for Flutter to actually add this pointer, we need to set the - // signal to none. - signalKind: ui.PointerSignalKind.none, - device: deviceId, - physicalX: clientX * ui.window.devicePixelRatio, - physicalY: clientY * ui.window.devicePixelRatio, - buttons: buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - scrollDeltaX: 0, - scrollDeltaY: 0, - )); -} - List _convertWheelEventToPointerData( html.WheelEvent event, ) { @@ -543,25 +493,23 @@ List _convertWheelEventToPointerData( break; } - final List data = []; - _ensureMouseDeviceAdded(data, event.client.x, event.client.y, event.buttons, - event.timeStamp, _mouseDeviceId); - data.add(ui.PointerData( - change: ui.PointerChange.hover, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.scroll, - device: _mouseDeviceId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - scrollDeltaX: deltaX, - scrollDeltaY: deltaY, - )); - return data; + return [ + ui.PointerData( + change: ui.PointerChange.hover, + timeStamp: _eventTimeStampToDuration(event.timeStamp), + kind: ui.PointerDeviceKind.mouse, + signalKind: ui.PointerSignalKind.scroll, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: event.buttons, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + scrollDeltaX: deltaX, + scrollDeltaY: deltaY, + ) + ]; } void _addWheelEventListener(void listener(html.WheelEvent e)) { diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart new file mode 100644 index 0000000000000..edce78486aa30 --- /dev/null +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -0,0 +1,246 @@ +// 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. + +part of engine; + +class _PointerState { + _PointerState(this.x, this.y); + + int get pointer => _pointer; // The identifier used in PointerEvent objects. + int _pointer; + static int _pointerCount = 0; + void startNewPointer() { + _pointerCount += 1; + _pointer = _pointerCount; + } + + bool down = false; + + double x; + double y; +} + +/// Converter to convert web pointer data to a framework-compatible form. +/// +/// This converter calculates pointer location delta and pointer identifier for +/// each pointer. Both are required by framework to correctly trigger gesture +/// activity. It also attempts to sanitize pointer data input sequence by always +/// synthesizing an add pointer data prior to hover or down if it the pointer is +/// not previously added. +/// +/// For example: +/// before: +/// hover -> down -> move -> up +/// after: +/// add(synthesize) -> hover -> down -> move -> up +/// +/// before: +/// down -> move -> up +/// after: +/// add(synthesize) -> down -> move -> up +class PointerDataConverter { + PointerDataConverter._(); + + // Map from platform pointer identifiers to PointerEvent pointer identifiers. + // Static to guarantee that pointers are unique. + static final Map _pointers = {}; + + static _PointerState _ensureStateForPointer(ui.PointerData datum, double x, double y) { + return _pointers.putIfAbsent( + datum.device, + () => _PointerState(x, y), + ); + } + + static ui.PointerData _generateCompletePointerData(ui.PointerData datum) { + assert(_pointers.containsKey(datum.device)); + final _PointerState state = _pointers[datum.device]; + final double deltaX = datum.physicalX - state.x; + final double deltaY = datum.physicalY - state.y; + state.x = datum.physicalX; + state.y = datum.physicalY; + return ui.PointerData( + timeStamp: datum.timeStamp, + change: datum.change, + kind: datum.kind, + signalKind: datum.signalKind, + device: datum.device, + pointerIdentifier: state.pointer ?? 0, + physicalX: datum.physicalX, + physicalY: datum.physicalY, + physicalDeltaX: deltaX, + physicalDeltaY: deltaY, + buttons: datum.buttons, + obscured: datum.obscured, + synthesized: datum.synthesized, + pressure: datum.pressure, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + size: datum.size, + radiusMajor: datum.radiusMajor, + radiusMinor: datum.radiusMinor, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt, + platformData: datum.platformData, + scrollDeltaX: datum.scrollDeltaX, + scrollDeltaY: datum.scrollDeltaY, + ); + } + + static bool _locationHasChanged(ui.PointerData datum) { + assert(_pointers.containsKey(datum.device)); + final _PointerState state = _pointers[datum.device]; + return state.x != datum.physicalX || state.y != datum.physicalY; + } + + static ui.PointerData _synthesizeFrom(ui.PointerData datum, ui.PointerChange change) { + assert(_pointers.containsKey(datum.device)); + final _PointerState state = _pointers[datum.device]; + final double deltaX = datum.physicalX - state.x; + final double deltaY = datum.physicalY - state.y; + state.x = datum.physicalX; + state.y = datum.physicalY; + return ui.PointerData( + timeStamp: datum.timeStamp, + change: change, + kind: datum.kind, + // All the pointer data except scroll should not have a signal kind, and + // there is no use case for synthetic scroll event. We should be + // safe to default it to ui.PointerSignalKind.none. + signalKind: ui.PointerSignalKind.none, + device: datum.device, + pointerIdentifier: state.pointer ?? 0, + physicalX: datum.physicalX, + physicalY: datum.physicalY, + physicalDeltaX: deltaX, + physicalDeltaY: deltaY, + buttons: datum.buttons, + obscured: datum.obscured, + synthesized: true, + pressure: datum.pressure, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + size: datum.size, + radiusMajor: datum.radiusMajor, + radiusMinor: datum.radiusMinor, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt, + platformData: datum.platformData, + scrollDeltaX: datum.scrollDeltaX, + scrollDeltaY: datum.scrollDeltaY, + ); + } + + /// Convert the given list pointer data into a sequence of framework-compatible + /// pointer data. + static Iterable convert(List data) sync* { + for (ui.PointerData datum in data) { + assert(datum.change != null); + if (datum.signalKind == null || + datum.signalKind == ui.PointerSignalKind.none) { + switch (datum.change) { + case ui.PointerChange.add: + assert(!_pointers.containsKey(datum.device)); + _ensureStateForPointer(datum, datum.physicalX, datum.physicalY); + assert(!_locationHasChanged(datum)); + yield _generateCompletePointerData(datum); + break; + case ui.PointerChange.hover: + final bool alreadyAdded = _pointers.containsKey(datum.device); + final _PointerState state = _ensureStateForPointer( + datum, datum.physicalX, datum.physicalY); + assert(!state.down); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + yield _synthesizeFrom(datum, ui.PointerChange.add); + } + yield _generateCompletePointerData(datum); + break; + case ui.PointerChange.down: + final bool alreadyAdded = _pointers.containsKey(datum.device); + final _PointerState state = _ensureStateForPointer( + datum, datum.physicalX, datum.physicalY); + assert(!state.down); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + yield _synthesizeFrom(datum, ui.PointerChange.add); + } + assert(!_locationHasChanged(datum)); + state.startNewPointer(); + state.down = true; + yield _generateCompletePointerData(datum); + break; + case ui.PointerChange.move: + final bool alreadyAdded = _pointers.containsKey(datum.device); + final _PointerState state = _ensureStateForPointer( + datum, datum.physicalX, datum.physicalY); + if (!alreadyAdded) { + // Synthesizes an add pointer data and down pointer data. + yield _synthesizeFrom(datum, ui.PointerChange.add); + yield _synthesizeFrom(datum, ui.PointerChange.down); + } + assert(state.down); + yield _generateCompletePointerData(datum); + break; + case ui.PointerChange.up: + case ui.PointerChange.cancel: + assert(_pointers.containsKey(datum.device)); + final _PointerState state = _pointers[datum.device]; + assert(state.down); + assert(!_locationHasChanged(datum)); + state.down = false; + yield _generateCompletePointerData(datum); + break; + case ui.PointerChange.remove: + assert(_pointers.containsKey(datum.device)); + final _PointerState state = _pointers[datum.device]; + assert(!state.down); + assert(!_locationHasChanged(datum)); + _pointers.remove(datum.device); + yield _generateCompletePointerData(datum); + break; + } + } else { + switch (datum.signalKind) { + case ui.PointerSignalKind.scroll: + final bool alreadyAdded = _pointers.containsKey(datum.device); + final _PointerState state = _ensureStateForPointer( + datum, datum.physicalX, datum.physicalY); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + yield _synthesizeFrom(datum, ui.PointerChange.add); + } + if (_locationHasChanged(datum)) { + // Synthesize a hover/move of the pointer to the scroll location + // before sending the scroll event, if necessary, so that clients + // don't have to worry about native ordering of hover and scroll + // events. + if (state.down) { + yield _synthesizeFrom(datum, ui.PointerChange.move); + } else { + yield _synthesizeFrom(datum, ui.PointerChange.hover); + } + } + yield _generateCompletePointerData(datum); + break; + case ui.PointerSignalKind.none: + assert(false); // This branch should already have 'none' filtered out. + break; + case ui.PointerSignalKind.unknown: + // Ignore unknown signals. + break; + } + } + } + } + +} \ No newline at end of file From 86320f549e9f4512ce8507dc12702d2f04b2642e Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 2 Dec 2019 15:20:23 -0800 Subject: [PATCH 2/5] fix license golden --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1e81f43602348..b1a615ee3a031 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -405,6 +405,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/render_vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/rrect_renderer.dart From 42bb5e94a7cc09273563f7d79fc3251cf7d984f9 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 2 Dec 2019 16:49:58 -0800 Subject: [PATCH 3/5] added test --- .../lib/src/engine/pointer_binding.dart | 8 +- .../lib/src/engine/pointer_converter.dart | 23 +- .../test/engine/pointer_binding_test.dart | 290 +++++++++++++++++- 3 files changed, 305 insertions(+), 16 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 9d564661ca289..bc369fd73b9b0 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -20,10 +20,12 @@ class PointerBinding { _instance = this; _detector = const PointerSupportDetector(); _adapter = _createAdapter(); + _pointerDataConverter = PointerDataConverter(); } assert(() { registerHotRestartListener(() { _adapter?.clearListeners(); + _pointerDataConverter?.clearPointerState(); }); return true; }()); @@ -32,7 +34,7 @@ class PointerBinding { final DomRenderer domRenderer; PointerSupportDetector _detector; BaseAdapter _adapter; - + PointerDataConverter _pointerDataConverter; /// Should be used in tests to define custom detection of pointer support. /// /// ```dart @@ -57,6 +59,7 @@ class PointerBinding { _detector = newDetector; _adapter?.clearListeners(); _adapter = _createAdapter(); + _pointerDataConverter?.clearPointerState(); } } @@ -75,9 +78,8 @@ class PointerBinding { void _onPointerData(List data) { final List converted = List.from( - PointerDataConverter.convert(data), + _pointerDataConverter.convert(data), ); - print('from data ${data} to converted ${converted}'); final ui.PointerDataPacket packet = ui.PointerDataPacket(data: converted); final ui.PointerDataPacketCallback callback = ui.window.onPointerDataPacket; if (callback != null) { diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index edce78486aa30..89256d7789361 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -40,20 +40,29 @@ class _PointerState { /// after: /// add(synthesize) -> down -> move -> up class PointerDataConverter { - PointerDataConverter._(); + PointerDataConverter(); // Map from platform pointer identifiers to PointerEvent pointer identifiers. // Static to guarantee that pointers are unique. - static final Map _pointers = {}; + final Map _pointers = {}; - static _PointerState _ensureStateForPointer(ui.PointerData datum, double x, double y) { + /// Clears the existing pointer states. + /// + /// This method is invoked during hot reload to make sure we have a clean + /// converter after hot reload. + void clearPointerState() { + _pointers.clear(); + _PointerState._pointerCount = 0; + } + + _PointerState _ensureStateForPointer(ui.PointerData datum, double x, double y) { return _pointers.putIfAbsent( datum.device, () => _PointerState(x, y), ); } - static ui.PointerData _generateCompletePointerData(ui.PointerData datum) { + ui.PointerData _generateCompletePointerData(ui.PointerData datum) { assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; final double deltaX = datum.physicalX - state.x; @@ -92,13 +101,13 @@ class PointerDataConverter { ); } - static bool _locationHasChanged(ui.PointerData datum) { + bool _locationHasChanged(ui.PointerData datum) { assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; return state.x != datum.physicalX || state.y != datum.physicalY; } - static ui.PointerData _synthesizeFrom(ui.PointerData datum, ui.PointerChange change) { + ui.PointerData _synthesizeFrom(ui.PointerData datum, ui.PointerChange change) { assert(_pointers.containsKey(datum.device)); final _PointerState state = _pointers[datum.device]; final double deltaX = datum.physicalX - state.x; @@ -142,7 +151,7 @@ class PointerDataConverter { /// Convert the given list pointer data into a sequence of framework-compatible /// pointer data. - static Iterable convert(List data) sync* { + Iterable convert(List data) sync* { for (ui.PointerData datum in data) { assert(datum.change != null); if (datum.signalKind == null || diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 3638376cc6b8b..b52139ba92aa9 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -56,7 +56,11 @@ void main() { })); expect(packets, hasLength(3)); - expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + // 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)); }); @@ -78,10 +82,20 @@ void main() { })); expect(packets, hasLength(2)); - expect(packets[0].data[0].change, equals(ui.PointerChange.down)); + // 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[1].data[0].change, equals(ui.PointerChange.down)); + 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', () { @@ -99,10 +113,11 @@ void main() { 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 not create an add event if got a pointerdown', () { + test('does create an add event if got a pointerdown', () { List packets = []; ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { packets.add(packet); @@ -114,9 +129,272 @@ void main() { })); expect(packets, hasLength(1)); - expect(packets.single.data, 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(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'button': 1, + 'clientX': 10.0, + 'clientY': 10.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'button': 1, + 'clientX': 20.0, + 'clientY': 20.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerId': 1, + 'button': 1, + 'clientX': 20.0, + 'clientY': 20.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'button': 1, + 'clientX': 40.0, + 'clientY': 30.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointerup', { + 'pointerId': 1, + 'button': 1, + 'clientX': 40.0, + 'clientY': 30.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointermove', { + 'pointerId': 1, + 'button': 1, + 'clientX': 20.0, + 'clientY': 10.0, + })); + + glassPane.dispatchEvent(html.PointerEvent('pointerdown', { + 'pointerId': 1, + 'button': 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(html.PointerEvent('pointerdown', { + 'pointerId': -1, + '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(7)); + + // 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)); + // Scroll event will fire twice + expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[1].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[1].data[0].pointerIdentifier, equals(0)); + expect(packets[1].data[0].synthesized, equals(false)); + expect(packets[1].data[0].physicalX, equals(10.0)); + expect(packets[1].data[0].physicalY, equals(10.0)); + expect(packets[1].data[0].physicalDeltaX, equals(0.0)); + expect(packets[1].data[0].physicalDeltaY, equals(0.0)); + + // A hover will be synthesized. + expect(packets[2].data, hasLength(2)); + expect(packets[2].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[2].data[0].pointerIdentifier, equals(0)); + expect(packets[2].data[0].synthesized, equals(true)); + 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(10.0)); + expect(packets[2].data[0].physicalDeltaY, equals(40.0)); + + expect(packets[2].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[2].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[2].data[1].pointerIdentifier, equals(0)); + expect(packets[2].data[1].synthesized, equals(false)); + expect(packets[2].data[1].physicalX, equals(20.0)); + expect(packets[2].data[1].physicalY, equals(50.0)); + expect(packets[2].data[1].physicalDeltaX, equals(0.0)); + expect(packets[2].data[1].physicalDeltaY, equals(0.0)); + // Scroll event will fire twice + expect(packets[3].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[3].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[3].data[0].pointerIdentifier, equals(0)); + expect(packets[3].data[0].synthesized, equals(false)); + expect(packets[3].data[0].physicalX, equals(20.0)); + expect(packets[3].data[0].physicalY, equals(50.0)); + expect(packets[3].data[0].physicalDeltaX, equals(0.0)); + expect(packets[3].data[0].physicalDeltaY, equals(0.0)); + + expect(packets[4].data[0].change, equals(ui.PointerChange.down)); + expect(packets[4].data[0].signalKind, equals(null)); + expect(packets[4].data[0].pointerIdentifier, equals(1)); + expect(packets[4].data[0].synthesized, equals(false)); + expect(packets[4].data[0].physicalX, equals(20.0)); + expect(packets[4].data[0].physicalY, equals(50.0)); + expect(packets[4].data[0].physicalDeltaX, equals(0.0)); + expect(packets[4].data[0].physicalDeltaY, equals(0.0)); + + // A move will be synthesized because the button is currently down. + expect(packets[5].data, hasLength(2)); + expect(packets[5].data[0].change, equals(ui.PointerChange.move)); + expect(packets[5].data[0].pointerIdentifier, equals(1)); + expect(packets[5].data[0].synthesized, equals(true)); + expect(packets[5].data[0].physicalX, equals(30.0)); + expect(packets[5].data[0].physicalY, equals(60.0)); + expect(packets[5].data[0].physicalDeltaX, equals(10.0)); + expect(packets[5].data[0].physicalDeltaY, equals(10.0)); - expect(packets.single.data[0].change, equals(ui.PointerChange.down)); + expect(packets[5].data[1].change, equals(ui.PointerChange.hover)); + expect(packets[5].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[5].data[1].pointerIdentifier, equals(1)); + expect(packets[5].data[1].synthesized, equals(false)); + expect(packets[5].data[1].physicalX, equals(30.0)); + expect(packets[5].data[1].physicalY, equals(60.0)); + expect(packets[5].data[1].physicalDeltaX, equals(0.0)); + expect(packets[5].data[1].physicalDeltaY, equals(0.0)); + // Scroll event will fire twice + expect(packets[6].data[0].change, equals(ui.PointerChange.hover)); + expect(packets[6].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); + expect(packets[6].data[0].pointerIdentifier, equals(1)); + expect(packets[6].data[0].synthesized, equals(false)); + expect(packets[6].data[0].physicalX, equals(30.0)); + expect(packets[6].data[0].physicalY, equals(60.0)); + expect(packets[6].data[0].physicalDeltaX, equals(0.0)); + expect(packets[6].data[0].physicalDeltaY, equals(0.0)); }); }); } From 228def743403a3f12aab77aaed0aa9e40f900a9b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 2 Dec 2019 16:55:39 -0800 Subject: [PATCH 4/5] update --- lib/web_ui/lib/src/engine/pointer_converter.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index 89256d7789361..ce46ade102efb 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -7,7 +7,7 @@ part of engine; class _PointerState { _PointerState(this.x, this.y); - int get pointer => _pointer; // The identifier used in PointerEvent objects. + int get pointer => _pointer; // The identifier used in framework hit test. int _pointer; static int _pointerCount = 0; void startNewPointer() { @@ -21,7 +21,8 @@ class _PointerState { double y; } -/// Converter to convert web pointer data to a framework-compatible form. +/// Converter to convert web pointer data into a form that framework can +/// understand. /// /// This converter calculates pointer location delta and pointer identifier for /// each pointer. Both are required by framework to correctly trigger gesture @@ -42,8 +43,7 @@ class _PointerState { class PointerDataConverter { PointerDataConverter(); - // Map from platform pointer identifiers to PointerEvent pointer identifiers. - // Static to guarantee that pointers are unique. + // Map from browser pointer identifiers to PointerEvent pointer identifiers. final Map _pointers = {}; /// Clears the existing pointer states. From 71a15049773f9b9d503c262818598d5ea1d85bcb Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Tue, 3 Dec 2019 14:24:37 -0800 Subject: [PATCH 5/5] refactor pointer binding --- .../lib/src/engine/pointer_binding.dart | 228 +++--- .../lib/src/engine/pointer_converter.dart | 723 ++++++++++++++---- .../test/engine/pointer_binding_test.dart | 113 ++- 3 files changed, 728 insertions(+), 336 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index bc369fd73b9b0..46e0c2f751142 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -18,9 +18,9 @@ class PointerBinding { PointerBinding(this.domRenderer) { if (_instance == null) { _instance = this; + _pointerDataConverter = PointerDataConverter(); _detector = const PointerSupportDetector(); _adapter = _createAdapter(); - _pointerDataConverter = PointerDataConverter(); } assert(() { registerHotRestartListener(() { @@ -65,22 +65,19 @@ class PointerBinding { BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return PointerAdapter(_onPointerData, domRenderer); + return PointerAdapter(_onPointerData, domRenderer, _pointerDataConverter); } if (_detector.hasTouchEvents) { - return TouchAdapter(_onPointerData, domRenderer); + return TouchAdapter(_onPointerData, domRenderer, _pointerDataConverter); } if (_detector.hasMouseEvents) { - return MouseAdapter(_onPointerData, domRenderer); + return MouseAdapter(_onPointerData, domRenderer, _pointerDataConverter); } return null; } void _onPointerData(List data) { - final List converted = List.from( - _pointerDataConverter.convert(data), - ); - final ui.PointerDataPacket packet = ui.PointerDataPacket(data: converted); + final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data); final ui.PointerDataPacketCallback callback = ui.window.onPointerDataPacket; if (callback != null) { callback(packet); @@ -120,11 +117,19 @@ class _PressedButton { /// Common functionality that's shared among adapters. abstract class BaseAdapter { - static final Map _listeners = - {}; + BaseAdapter(this._callback, this.domRenderer, this._pointerDataConverter) { + _setup(); + } + /// Listeners that are registered through dart to js api. + static final Map _listeners = + {}; + /// Listeners that are registered through native javascript api. + static final Map _nativeListeners = + {}; final DomRenderer domRenderer; PointerDataCallback _callback; + PointerDataConverter _pointerDataConverter; // A set of the buttons that are currently being pressed. Set<_PressedButton> _pressedButtons = Set<_PressedButton>(); @@ -141,10 +146,6 @@ abstract class BaseAdapter { } } - BaseAdapter(this._callback, this.domRenderer) { - _setup(); - } - /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. void _setup(); @@ -153,9 +154,21 @@ abstract class BaseAdapter { void clearListeners() { final html.Element glassPane = domRenderer.glassPaneElement; _listeners.forEach((String eventName, html.EventListener listener) { - glassPane.removeEventListener(eventName, listener, true); + glassPane.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, + 'removeEventListener', [ + 'wheel', + listener, + ] + ); }); _listeners.clear(); + _nativeListeners.clear(); } void _addEventListener(String eventName, html.EventListener handler) { @@ -174,6 +187,75 @@ abstract class BaseAdapter { domRenderer.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) { + final int ms = milliseconds.toInt(); + final int micro = + ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt(); + return Duration(milliseconds: ms, microseconds: micro); + } + + List _convertWheelEventToPointerData( + html.WheelEvent event, + ) { + const int domDeltaPixel = 0x00; + const int domDeltaLine = 0x01; + const int domDeltaPage = 0x02; + + // Flutter only supports pixel scroll delta. Convert deltaMode values + // to pixels. + double deltaX = event.deltaX; + double deltaY = event.deltaY; + switch (event.deltaMode) { + case domDeltaLine: + deltaX *= 32.0; + deltaY *= 32.0; + break; + case domDeltaPage: + deltaX *= ui.window.physicalSize.width; + deltaY *= ui.window.physicalSize.height; + break; + case domDeltaPixel: + default: + break; + } + final List data = []; + _pointerDataConverter.convert( + data, + change: ui.PointerChange.hover, + timeStamp: _eventTimeStampToDuration(event.timeStamp), + kind: ui.PointerDeviceKind.mouse, + signalKind: ui.PointerSignalKind.scroll, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: event.buttons, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + scrollDeltaX: deltaX, + scrollDeltaY: deltaY, + ); + return data; + } + + 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; + js_util.setProperty(eventOptions, 'passive', false); + js_util.callMethod( + domRenderer.glassPaneElement, + 'addEventListener', [ + 'wheel', + jsHandler, + eventOptions + ] + ); + + } } const int _kPrimaryMouseButton = 0x1; @@ -204,8 +286,11 @@ int _deviceFromHtmlEvent(event) { /// Adapter class to be used with browsers that support native pointer events. class PointerAdapter extends BaseAdapter { - PointerAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); + PointerAdapter( + PointerDataCallback callback, + DomRenderer domRenderer, + PointerDataConverter _pointerDataConverter + ) : super(callback, domRenderer, _pointerDataConverter); @override void _setup() { @@ -258,7 +343,8 @@ class PointerAdapter extends BaseAdapter { _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); }); - _addWheelEventListener((html.WheelEvent event) { + _addWheelEventListener((html.Event event) { + assert(event is html.WheelEvent); if (_debugLogPointerEvents) { print(event.type); } @@ -277,7 +363,8 @@ class PointerAdapter extends BaseAdapter { final List data = []; for (int i = 0; i < allEvents.length; i++) { final html.PointerEvent event = allEvents[i]; - data.add(ui.PointerData( + _pointerDataConverter.convert( + data, change: change, timeStamp: _eventTimeStampToDuration(event.timeStamp), kind: _pointerTypeToDeviceKind(event.pointerType), @@ -289,7 +376,7 @@ class PointerAdapter extends BaseAdapter { pressureMin: 0.0, pressureMax: 1.0, tilt: _computeHighestTilt(event), - )); + ); } return data; } @@ -331,8 +418,11 @@ class PointerAdapter extends BaseAdapter { /// Adapter to be used with browsers that support touch events. class TouchAdapter extends BaseAdapter { - TouchAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); + TouchAdapter( + PointerDataCallback callback, + DomRenderer domRenderer, + PointerDataConverter _pointerDataConverter + ) : super(callback, domRenderer, _pointerDataConverter); @override void _setup() { @@ -369,11 +459,12 @@ class TouchAdapter extends BaseAdapter { html.TouchEvent event, ) { final html.TouchList touches = event.changedTouches; - final List data = List(touches.length); + final List data = List(); final int len = touches.length; for (int i = 0; i < len; i++) { final html.Touch touch = touches[i]; - data[i] = ui.PointerData( + _pointerDataConverter.convert( + data, change: change, timeStamp: _eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.touch, @@ -396,8 +487,11 @@ const int _mouseDeviceId = -1; /// Adapter to be used with browsers that support mouse events. class MouseAdapter extends BaseAdapter { - MouseAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); + MouseAdapter( + PointerDataCallback callback, + DomRenderer domRenderer, + PointerDataConverter _pointerDataConverter + ) : super(callback, domRenderer, _pointerDataConverter); @override void _setup() { @@ -430,7 +524,8 @@ class MouseAdapter extends BaseAdapter { _callback(_convertEventToPointerData(ui.PointerChange.up, event)); }); - _addWheelEventListener((html.WheelEvent event) { + _addWheelEventListener((html.Event event) { + assert(event is html.WheelEvent); if (_debugLogPointerEvents) { print(event.type); } @@ -443,64 +538,13 @@ class MouseAdapter extends BaseAdapter { ui.PointerChange change, html.MouseEvent event, ) { - return [ - ui.PointerData( - change: change, - timeStamp: _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, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - ) - ]; - } -} - -/// Convert a floating number timestamp (in milliseconds) to a [Duration] by -/// splitting it into two integer components: milliseconds + microseconds. -Duration _eventTimeStampToDuration(num milliseconds) { - final int ms = milliseconds.toInt(); - final int micro = - ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt(); - return Duration(milliseconds: ms, microseconds: micro); -} - -List _convertWheelEventToPointerData( - html.WheelEvent event, -) { - const int domDeltaPixel = 0x00; - const int domDeltaLine = 0x01; - const int domDeltaPage = 0x02; - - // Flutter only supports pixel scroll delta. Convert deltaMode values - // to pixels. - double deltaX = event.deltaX; - double deltaY = event.deltaY; - switch (event.deltaMode) { - case domDeltaLine: - deltaX *= 32.0; - deltaY *= 32.0; - break; - case domDeltaPage: - deltaX *= ui.window.physicalSize.width; - deltaY *= ui.window.physicalSize.height; - break; - case domDeltaPixel: - default: - break; - } - - return [ - ui.PointerData( - change: ui.PointerChange.hover, + List data = []; + _pointerDataConverter.convert( + data, + change: change, timeStamp: _eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.scroll, + signalKind: ui.PointerSignalKind.none, device: _mouseDeviceId, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, @@ -508,19 +552,7 @@ List _convertWheelEventToPointerData( pressure: 1.0, pressureMin: 0.0, pressureMax: 1.0, - scrollDeltaX: deltaX, - scrollDeltaY: deltaY, - ) - ]; -} - -void _addWheelEventListener(void listener(html.WheelEvent e)) { - final dynamic eventOptions = js_util.newObject(); - js_util.setProperty(eventOptions, 'passive', false); - js_util.callMethod(PointerBinding.instance.domRenderer.glassPaneElement, - 'addEventListener', [ - 'wheel', - js.allowInterop((html.WheelEvent event) => listener(event)), - eventOptions - ]); + ); + return data; + } } diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index ce46ade102efb..c6b081558913e 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -7,7 +7,8 @@ part of engine; class _PointerState { _PointerState(this.x, this.y); - int get pointer => _pointer; // The identifier used in framework hit test. + /// The identifier used in framework hit test. + int get pointer => _pointer; int _pointer; static int _pointerCount = 0; void startNewPointer() { @@ -55,201 +56,585 @@ class PointerDataConverter { _PointerState._pointerCount = 0; } - _PointerState _ensureStateForPointer(ui.PointerData datum, double x, double y) { + _PointerState _ensureStateForPointer(int device, double x, double y) { return _pointers.putIfAbsent( - datum.device, - () => _PointerState(x, y), + device, + () => _PointerState(x, y), ); } - ui.PointerData _generateCompletePointerData(ui.PointerData datum) { - assert(_pointers.containsKey(datum.device)); - final _PointerState state = _pointers[datum.device]; - final double deltaX = datum.physicalX - state.x; - final double deltaY = datum.physicalY - state.y; - state.x = datum.physicalX; - state.y = datum.physicalY; + ui.PointerData _generateCompletePointerData({ + Duration timeStamp, + ui.PointerChange change, + ui.PointerDeviceKind kind, + ui.PointerSignalKind signalKind, + int device, + double physicalX, + double physicalY, + int buttons, + bool obscured, + double pressure, + double pressureMin, + double pressureMax, + double distance, + double distanceMax, + double size, + double radiusMajor, + double radiusMinor, + double radiusMin, + double radiusMax, + double orientation, + double tilt, + int platformData, + double scrollDeltaX, + double scrollDeltaY, + }) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + final double deltaX = physicalX - state.x; + final double deltaY = physicalY - state.y; + state.x = physicalX; + state.y = physicalY; return ui.PointerData( - timeStamp: datum.timeStamp, - change: datum.change, - kind: datum.kind, - signalKind: datum.signalKind, - device: datum.device, + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, pointerIdentifier: state.pointer ?? 0, - physicalX: datum.physicalX, - physicalY: datum.physicalY, + physicalX: physicalX, + physicalY: physicalY, physicalDeltaX: deltaX, physicalDeltaY: deltaY, - buttons: datum.buttons, - obscured: datum.obscured, - synthesized: datum.synthesized, - pressure: datum.pressure, - pressureMin: datum.pressureMin, - pressureMax: datum.pressureMax, - distance: datum.distance, - distanceMax: datum.distanceMax, - size: datum.size, - radiusMajor: datum.radiusMajor, - radiusMinor: datum.radiusMinor, - radiusMin: datum.radiusMin, - radiusMax: datum.radiusMax, - orientation: datum.orientation, - tilt: datum.tilt, - platformData: datum.platformData, - scrollDeltaX: datum.scrollDeltaX, - scrollDeltaY: datum.scrollDeltaY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, ); } - bool _locationHasChanged(ui.PointerData datum) { - assert(_pointers.containsKey(datum.device)); - final _PointerState state = _pointers[datum.device]; - return state.x != datum.physicalX || state.y != datum.physicalY; + bool _locationHasChanged(int device, double physicalX, double physicalY) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + return state.x != physicalX || state.y != physicalY; } - ui.PointerData _synthesizeFrom(ui.PointerData datum, ui.PointerChange change) { - assert(_pointers.containsKey(datum.device)); - final _PointerState state = _pointers[datum.device]; - final double deltaX = datum.physicalX - state.x; - final double deltaY = datum.physicalY - state.y; - state.x = datum.physicalX; - state.y = datum.physicalY; + ui.PointerData _synthesizePointerData({ + Duration timeStamp, + ui.PointerChange change, + ui.PointerDeviceKind kind, + int device, + double physicalX, + double physicalY, + int buttons, + bool obscured, + double pressure, + double pressureMin, + double pressureMax, + double distance, + double distanceMax, + double size, + double radiusMajor, + double radiusMinor, + double radiusMin, + double radiusMax, + double orientation, + double tilt, + int platformData, + double scrollDeltaX, + double scrollDeltaY, + }) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + final double deltaX = physicalX - state.x; + final double deltaY = physicalY - state.y; + state.x = physicalX; + state.y = physicalY; return ui.PointerData( - timeStamp: datum.timeStamp, + timeStamp: timeStamp, change: change, - kind: datum.kind, + kind: kind, // All the pointer data except scroll should not have a signal kind, and // there is no use case for synthetic scroll event. We should be // safe to default it to ui.PointerSignalKind.none. signalKind: ui.PointerSignalKind.none, - device: datum.device, + device: device, pointerIdentifier: state.pointer ?? 0, - physicalX: datum.physicalX, - physicalY: datum.physicalY, + physicalX: physicalX, + physicalY: physicalY, physicalDeltaX: deltaX, physicalDeltaY: deltaY, - buttons: datum.buttons, - obscured: datum.obscured, + buttons: buttons, + obscured: obscured, synthesized: true, - pressure: datum.pressure, - pressureMin: datum.pressureMin, - pressureMax: datum.pressureMax, - distance: datum.distance, - distanceMax: datum.distanceMax, - size: datum.size, - radiusMajor: datum.radiusMajor, - radiusMinor: datum.radiusMinor, - radiusMin: datum.radiusMin, - radiusMax: datum.radiusMax, - orientation: datum.orientation, - tilt: datum.tilt, - platformData: datum.platformData, - scrollDeltaX: datum.scrollDeltaX, - scrollDeltaY: datum.scrollDeltaY, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, ); } - /// Convert the given list pointer data into a sequence of framework-compatible - /// pointer data. - Iterable convert(List data) sync* { - for (ui.PointerData datum in data) { - assert(datum.change != null); - if (datum.signalKind == null || - datum.signalKind == ui.PointerSignalKind.none) { - switch (datum.change) { - case ui.PointerChange.add: - assert(!_pointers.containsKey(datum.device)); - _ensureStateForPointer(datum, datum.physicalX, datum.physicalY); - assert(!_locationHasChanged(datum)); - yield _generateCompletePointerData(datum); - break; - case ui.PointerChange.hover: - final bool alreadyAdded = _pointers.containsKey(datum.device); - final _PointerState state = _ensureStateForPointer( - datum, datum.physicalX, datum.physicalY); - assert(!state.down); - if (!alreadyAdded) { - // Synthesizes an add pointer data. - yield _synthesizeFrom(datum, ui.PointerChange.add); - } - yield _generateCompletePointerData(datum); - break; - case ui.PointerChange.down: - final bool alreadyAdded = _pointers.containsKey(datum.device); - final _PointerState state = _ensureStateForPointer( - datum, datum.physicalX, datum.physicalY); - assert(!state.down); - if (!alreadyAdded) { - // Synthesizes an add pointer data. - yield _synthesizeFrom(datum, ui.PointerChange.add); - } - assert(!_locationHasChanged(datum)); - state.startNewPointer(); - state.down = true; - yield _generateCompletePointerData(datum); - break; - case ui.PointerChange.move: - final bool alreadyAdded = _pointers.containsKey(datum.device); - final _PointerState state = _ensureStateForPointer( - datum, datum.physicalX, datum.physicalY); - if (!alreadyAdded) { - // Synthesizes an add pointer data and down pointer data. - yield _synthesizeFrom(datum, ui.PointerChange.add); - yield _synthesizeFrom(datum, ui.PointerChange.down); - } - assert(state.down); - yield _generateCompletePointerData(datum); - break; - case ui.PointerChange.up: - case ui.PointerChange.cancel: - assert(_pointers.containsKey(datum.device)); - final _PointerState state = _pointers[datum.device]; - assert(state.down); - assert(!_locationHasChanged(datum)); - state.down = false; - yield _generateCompletePointerData(datum); - break; - case ui.PointerChange.remove: - assert(_pointers.containsKey(datum.device)); - final _PointerState state = _pointers[datum.device]; - assert(!state.down); - assert(!_locationHasChanged(datum)); - _pointers.remove(datum.device); - yield _generateCompletePointerData(datum); - break; - } - } else { - switch (datum.signalKind) { - case ui.PointerSignalKind.scroll: - final bool alreadyAdded = _pointers.containsKey(datum.device); - final _PointerState state = _ensureStateForPointer( - datum, datum.physicalX, datum.physicalY); - if (!alreadyAdded) { - // Synthesizes an add pointer data. - yield _synthesizeFrom(datum, ui.PointerChange.add); - } - if (_locationHasChanged(datum)) { - // Synthesize a hover/move of the pointer to the scroll location - // before sending the scroll event, if necessary, so that clients - // don't have to worry about native ordering of hover and scroll - // events. - if (state.down) { - yield _synthesizeFrom(datum, ui.PointerChange.move); - } else { - yield _synthesizeFrom(datum, ui.PointerChange.hover); - } + /// Converts the given html pointer event metrics into a sequence of framework-compatible + /// pointer data and stores it into [result] + void convert( + List result, { + Duration timeStamp = Duration.zero, + ui.PointerChange change = ui.PointerChange.cancel, + ui.PointerDeviceKind kind = ui.PointerDeviceKind.touch, + ui.PointerSignalKind signalKind, + int device = 0, + double physicalX = 0.0, + double physicalY = 0.0, + int buttons = 0, + bool obscured = false, + double pressure = 0.0, + double pressureMin = 0.0, + double pressureMax = 0.0, + double distance = 0.0, + double distanceMax = 0.0, + double size = 0.0, + double radiusMajor = 0.0, + double radiusMinor = 0.0, + double radiusMin = 0.0, + double radiusMax = 0.0, + double orientation = 0.0, + double tilt = 0.0, + int platformData = 0, + double scrollDeltaX = 0.0, + double scrollDeltaY = 0.0, + }) { + assert(change != null); + if (signalKind == null || + signalKind == ui.PointerSignalKind.none) { + switch (change) { + case ui.PointerChange.add: + assert(!_pointers.containsKey(device)); + _ensureStateForPointer(device, physicalX, physicalY); + assert(!_locationHasChanged(device, physicalX, physicalY)); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.hover: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + assert(!state.down); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.down: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + assert(!state.down); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + assert(!_locationHasChanged(device, physicalX, physicalY)); + state.startNewPointer(); + state.down = true; + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.move: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(state.down); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.up: + case ui.PointerChange.cancel: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(state.down); + assert(!_locationHasChanged(device, physicalX, physicalY)); + state.down = false; + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.remove: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(!state.down); + assert(!_locationHasChanged(device, physicalX, physicalY)); + _pointers.remove(device); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + } + } else { + switch (signalKind) { + case ui.PointerSignalKind.scroll: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + if (_locationHasChanged(device, physicalX, physicalY)) { + // Synthesize a hover/move of the pointer to the scroll location + // before sending the scroll event, if necessary, so that clients + // don't have to worry about native ordering of hover and scroll + // events. + if (state.down) { + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.move, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } else { + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.hover, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); } - yield _generateCompletePointerData(datum); - break; - case ui.PointerSignalKind.none: - assert(false); // This branch should already have 'none' filtered out. - break; - case ui.PointerSignalKind.unknown: - // Ignore unknown signals. - break; - } + } + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerSignalKind.none: + assert(false); // This branch should already have 'none' filtered out. + break; + case ui.PointerSignalKind.unknown: + // Ignore unknown signals. + break; } } } - -} \ No newline at end of file +} diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index b52139ba92aa9..cee6b8f16de14 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -301,7 +301,7 @@ void main() { deltaY: 10, )); - expect(packets, hasLength(7)); + expect(packets, hasLength(4)); // An add will be synthesized. expect(packets[0].data, hasLength(2)); @@ -321,80 +321,55 @@ void main() { 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)); - // Scroll event will fire twice - expect(packets[1].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[1].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[1].data[0].pointerIdentifier, equals(0)); - expect(packets[1].data[0].synthesized, equals(false)); - expect(packets[1].data[0].physicalX, equals(10.0)); - expect(packets[1].data[0].physicalY, equals(10.0)); - expect(packets[1].data[0].physicalDeltaX, equals(0.0)); - expect(packets[1].data[0].physicalDeltaY, equals(0.0)); // A hover will be synthesized. - expect(packets[2].data, hasLength(2)); - expect(packets[2].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[2].data[0].pointerIdentifier, equals(0)); - expect(packets[2].data[0].synthesized, equals(true)); + 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(null)); + 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(10.0)); - expect(packets[2].data[0].physicalDeltaY, equals(40.0)); - - expect(packets[2].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[2].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[2].data[1].pointerIdentifier, equals(0)); - expect(packets[2].data[1].synthesized, equals(false)); - expect(packets[2].data[1].physicalX, equals(20.0)); - expect(packets[2].data[1].physicalY, equals(50.0)); - expect(packets[2].data[1].physicalDeltaX, equals(0.0)); - expect(packets[2].data[1].physicalDeltaY, equals(0.0)); - // Scroll event will fire twice - expect(packets[3].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[3].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[3].data[0].pointerIdentifier, equals(0)); - expect(packets[3].data[0].synthesized, equals(false)); - expect(packets[3].data[0].physicalX, equals(20.0)); - expect(packets[3].data[0].physicalY, equals(50.0)); - expect(packets[3].data[0].physicalDeltaX, equals(0.0)); - expect(packets[3].data[0].physicalDeltaY, equals(0.0)); + expect(packets[2].data[0].physicalDeltaX, equals(0.0)); + expect(packets[2].data[0].physicalDeltaY, equals(0.0)); - expect(packets[4].data[0].change, equals(ui.PointerChange.down)); - expect(packets[4].data[0].signalKind, equals(null)); - expect(packets[4].data[0].pointerIdentifier, equals(1)); - expect(packets[4].data[0].synthesized, equals(false)); - expect(packets[4].data[0].physicalX, equals(20.0)); - expect(packets[4].data[0].physicalY, equals(50.0)); - expect(packets[4].data[0].physicalDeltaX, equals(0.0)); - expect(packets[4].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)); - // A move will be synthesized because the button is currently down. - expect(packets[5].data, hasLength(2)); - expect(packets[5].data[0].change, equals(ui.PointerChange.move)); - expect(packets[5].data[0].pointerIdentifier, equals(1)); - expect(packets[5].data[0].synthesized, equals(true)); - expect(packets[5].data[0].physicalX, equals(30.0)); - expect(packets[5].data[0].physicalY, equals(60.0)); - expect(packets[5].data[0].physicalDeltaX, equals(10.0)); - expect(packets[5].data[0].physicalDeltaY, equals(10.0)); - - expect(packets[5].data[1].change, equals(ui.PointerChange.hover)); - expect(packets[5].data[1].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[5].data[1].pointerIdentifier, equals(1)); - expect(packets[5].data[1].synthesized, equals(false)); - expect(packets[5].data[1].physicalX, equals(30.0)); - expect(packets[5].data[1].physicalY, equals(60.0)); - expect(packets[5].data[1].physicalDeltaX, equals(0.0)); - expect(packets[5].data[1].physicalDeltaY, equals(0.0)); - // Scroll event will fire twice - expect(packets[6].data[0].change, equals(ui.PointerChange.hover)); - expect(packets[6].data[0].signalKind, equals(ui.PointerSignalKind.scroll)); - expect(packets[6].data[0].pointerIdentifier, equals(1)); - expect(packets[6].data[0].synthesized, equals(false)); - expect(packets[6].data[0].physicalX, equals(30.0)); - expect(packets[6].data[0].physicalY, equals(60.0)); - expect(packets[6].data[0].physicalDeltaX, equals(0.0)); - expect(packets[6].data[0].physicalDeltaY, equals(0.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)); }); }); }