diff --git a/bin/internal/engine.version b/bin/internal/engine.version index a2784ed8fed..14e10a0f132 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -ce8b187914f599e8e579fab829671fb2f07064b7 +d1bc4c4850ee155430b3ff66609a225364048257 diff --git a/examples/layers/raw/touch_input.dart b/examples/layers/raw/touch_input.dart index 08f0103e9ea..f33d2765447 100644 --- a/examples/layers/raw/touch_input.dart +++ b/examples/layers/raw/touch_input.dart @@ -79,15 +79,15 @@ void beginFrame(Duration timeStamp) { void handlePointerDataPacket(ui.PointerDataPacket packet) { // The pointer packet contains a number of pointer movements, which we iterate // through and process. - for (ui.PointerData pointer in packet.pointers) { - if (pointer.change == ui.PointerChange.down) { + for (ui.PointerData datum in packet.data) { + if (datum.change == ui.PointerChange.down) { // If the pointer went down, we change the color of the circle to blue. color = const ui.Color(0xFF0000FF); // Rather than calling paint() synchronously, we ask the engine to // schedule a frame. The engine will call onBeginFrame when it is actually // time to produce the frame. ui.window.scheduleFrame(); - } else if (pointer.change == ui.PointerChange.up) { + } else if (datum.change == ui.PointerChange.up) { // Similarly, if the pointer went up, we change the color of the circle to // green and schedule a frame. It's harmless to call scheduleFrame many // times because the engine will ignore redundant requests up until the diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart index e81af639125..9c3a04cbd49 100644 --- a/packages/flutter/lib/src/gestures/binding.dart +++ b/packages/flutter/lib/src/gestures/binding.dart @@ -29,7 +29,7 @@ abstract class GestureBinding extends BindingBase implements HitTestable, HitTes static GestureBinding _instance; void _handlePointerDataPacket(ui.PointerDataPacket packet) { - _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.pointers, ui.window.devicePixelRatio)); + _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, ui.window.devicePixelRatio)); _flushPointerEventQueue(); } diff --git a/packages/flutter/lib/src/gestures/converter.dart b/packages/flutter/lib/src/gestures/converter.dart index 76c71e3c785..508347b8a03 100644 --- a/packages/flutter/lib/src/gestures/converter.dart +++ b/packages/flutter/lib/src/gestures/converter.dart @@ -36,6 +36,13 @@ class PointerEventConverter { // Map from platform pointer identifiers to PointerEvent pointer identifiers. static Map _pointers = {}; + static _PointerState _ensureStateForPointer(ui.PointerData datum, Point position) { + return _pointers.putIfAbsent( + datum.device, + () => new _PointerState(position) + ); + } + /// Expand the given packet of pointer data into a sequence of framework pointer events. static Iterable expand(Iterable data, double devicePixelRatio) sync* { for (ui.PointerData datum in data) { @@ -43,19 +50,14 @@ class PointerEventConverter { final Duration timeStamp = datum.timeStamp; final PointerDeviceKind kind = datum.kind; switch (datum.change) { - case ui.PointerChange.down: - assert(!_pointers.containsKey(datum.pointer)); - _PointerState state = _pointers.putIfAbsent( - datum.pointer, - () => new _PointerState(position) - ); + case ui.PointerChange.add: + assert(!_pointers.containsKey(datum.device)); + _PointerState state = _ensureStateForPointer(datum, position); assert(state.lastPosition == position); - state.startNewPointer(); - state.setDown(); yield new PointerAddedEvent( timeStamp: timeStamp, - pointer: state.pointer, kind: kind, + device: datum.device, position: position, obscured: datum.obscured, pressureMin: datum.pressureMin, @@ -67,10 +69,108 @@ class PointerEventConverter { orientation: datum.orientation, tilt: datum.tilt ); + break; + case ui.PointerChange.hover: + final bool alreadyAdded = _pointers.containsKey(datum.device); + _PointerState state = _ensureStateForPointer(datum, position); + assert(!state.down); + if (!alreadyAdded) { + assert(state.lastPosition == position); + yield new PointerAddedEvent( + timeStamp: timeStamp, + kind: kind, + device: datum.device, + position: position, + obscured: datum.obscured, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt + ); + } + Offset offset = position - state.lastPosition; + state.lastPosition = position; + yield new PointerHoverEvent( + timeStamp: timeStamp, + kind: kind, + device: datum.device, + position: position, + delta: offset, + buttons: datum.buttons, + obscured: datum.obscured, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + radiusMajor: datum.radiusMajor, + radiusMinor: datum.radiusMajor, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt + ); + state.lastPosition = position; + break; + case ui.PointerChange.down: + final bool alreadyAdded = _pointers.containsKey(datum.device); + _PointerState state = _ensureStateForPointer(datum, position); + assert(!state.down); + if (!alreadyAdded) { + assert(state.lastPosition == position); + yield new PointerAddedEvent( + timeStamp: timeStamp, + kind: kind, + device: datum.device, + position: position, + obscured: datum.obscured, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt + ); + } + if (state.lastPosition != position) { + // Not all sources of pointer packets respect the invariant that + // they hover the pointer to the down location before sending the + // down event. We restore the invariant here for our clients. + Offset offset = position - state.lastPosition; + state.lastPosition = position; + yield new PointerHoverEvent( + timeStamp: timeStamp, + kind: kind, + device: datum.device, + position: position, + delta: offset, + buttons: datum.buttons, + obscured: datum.obscured, + pressureMin: datum.pressureMin, + pressureMax: datum.pressureMax, + distance: datum.distance, + distanceMax: datum.distanceMax, + radiusMajor: datum.radiusMajor, + radiusMinor: datum.radiusMajor, + radiusMin: datum.radiusMin, + radiusMax: datum.radiusMax, + orientation: datum.orientation, + tilt: datum.tilt + ); + state.lastPosition = position; + } + state.startNewPointer(); + state.setDown(); yield new PointerDownEvent( timeStamp: timeStamp, pointer: state.pointer, kind: kind, + device: datum.device, position: position, buttons: datum.buttons, obscured: datum.obscured, @@ -90,8 +190,8 @@ class PointerEventConverter { // If the service starts supporting hover pointers, then it must also // start sending us ADDED and REMOVED data points. // See also: https://github.com/flutter/flutter/issues/720 - assert(_pointers.containsKey(datum.pointer)); - _PointerState state = _pointers[datum.pointer]; + assert(_pointers.containsKey(datum.device)); + _PointerState state = _pointers[datum.device]; assert(state.down); Offset offset = position - state.lastPosition; state.lastPosition = position; @@ -99,15 +199,14 @@ class PointerEventConverter { timeStamp: timeStamp, pointer: state.pointer, kind: kind, + device: datum.device, position: position, delta: offset, - down: state.down, buttons: datum.buttons, obscured: datum.obscured, pressure: datum.pressure, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, - distance: datum.distance, distanceMax: datum.distanceMax, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, @@ -119,8 +218,8 @@ class PointerEventConverter { break; case ui.PointerChange.up: case ui.PointerChange.cancel: - assert(_pointers.containsKey(datum.pointer)); - _PointerState state = _pointers[datum.pointer]; + assert(_pointers.containsKey(datum.device)); + _PointerState state = _pointers[datum.device]; assert(state.down); if (position != state.lastPosition) { // Not all sources of pointer packets respect the invariant that @@ -134,15 +233,14 @@ class PointerEventConverter { timeStamp: timeStamp, pointer: state.pointer, kind: kind, + device: datum.device, position: position, delta: offset, - down: state.down, buttons: datum.buttons, obscured: datum.obscured, pressure: datum.pressure, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, - distance: datum.distance, distanceMax: datum.distanceMax, radiusMajor: datum.radiusMajor, radiusMinor: datum.radiusMajor, @@ -160,6 +258,7 @@ class PointerEventConverter { timeStamp: timeStamp, pointer: state.pointer, kind: kind, + device: datum.device, position: position, buttons: datum.buttons, obscured: datum.obscured, @@ -176,6 +275,7 @@ class PointerEventConverter { timeStamp: timeStamp, pointer: state.pointer, kind: kind, + device: datum.device, position: position, buttons: datum.buttons, obscured: datum.obscured, @@ -189,10 +289,13 @@ class PointerEventConverter { tilt: datum.tilt ); } + _pointers.remove(datum.device); + break; + case ui.PointerChange.remove: yield new PointerRemovedEvent( timeStamp: timeStamp, - pointer: state.pointer, kind: kind, + device: datum.device, obscured: datum.obscured, pressureMin: datum.pressureMin, pressureMax: datum.pressureMax, @@ -200,7 +303,6 @@ class PointerEventConverter { radiusMin: datum.radiusMin, radiusMax: datum.radiusMax ); - _pointers.remove(datum.pointer); break; default: // TODO(ianh): once https://github.com/flutter/flutter/issues/720 is diff --git a/packages/flutter/lib/src/gestures/events.dart b/packages/flutter/lib/src/gestures/events.dart index b8abc8ae7b8..10e2e799860 100644 --- a/packages/flutter/lib/src/gestures/events.dart +++ b/packages/flutter/lib/src/gestures/events.dart @@ -76,6 +76,7 @@ abstract class PointerEvent { this.timeStamp: Duration.ZERO, this.pointer: 0, this.kind: PointerDeviceKind.touch, + this.device: 0, this.position: Point.origin, this.delta: Offset.zero, this.buttons: 0, @@ -103,6 +104,9 @@ abstract class PointerEvent { /// The kind of input device for which the event was generated. final PointerDeviceKind kind; + /// Unique identifier for the pointing device, reused across interactions. + final int device; + /// Coordinate of the position of the pointer, in logical pixels in the global /// coordinate space. final Point position; @@ -219,6 +223,7 @@ abstract class PointerEvent { 'timeStamp: $timeStamp, ' 'pointer: $pointer, ' 'kind: $kind, ' + 'device: $device, ' 'position: $position, ' 'delta: $delta, ' 'buttons: $buttons, ' @@ -250,8 +255,8 @@ class PointerAddedEvent extends PointerEvent { /// All of the argument must be non-null. const PointerAddedEvent({ Duration timeStamp: Duration.ZERO, - int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, Point position: Point.origin, bool obscured: false, double pressureMin: 1.0, @@ -264,8 +269,8 @@ class PointerAddedEvent extends PointerEvent { double tilt: 0.0 }) : super( timeStamp: timeStamp, - pointer: pointer, kind: kind, + device: device, position: position, obscured: obscured, pressureMin: pressureMin, @@ -289,8 +294,8 @@ class PointerRemovedEvent extends PointerEvent { /// All of the argument must be non-null. const PointerRemovedEvent({ Duration timeStamp: Duration.ZERO, - int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, bool obscured: false, double pressureMin: 1.0, double pressureMax: 1.0, @@ -299,8 +304,8 @@ class PointerRemovedEvent extends PointerEvent { double radiusMax: 0.0 }) : super( timeStamp: timeStamp, - pointer: pointer, kind: kind, + device: device, position: null, obscured: obscured, pressureMin: pressureMin, @@ -311,6 +316,57 @@ class PointerRemovedEvent extends PointerEvent { ); } +/// The pointer has moved with respect to the device while the pointer is not +/// in contact with the device. +/// +/// See also: +/// +/// * [PointerMoveEvent], which reports movement while the pointer is in +/// contact with the device. +class PointerHoverEvent extends PointerEvent { + /// Creates a pointer hover event. + /// + /// All of the argument must be non-null. + const PointerHoverEvent({ + Duration timeStamp: Duration.ZERO, + PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, + Point position: Point.origin, + Offset delta: Offset.zero, + int buttons: 0, + bool obscured: false, + double pressureMin: 1.0, + double pressureMax: 1.0, + double distance: 0.0, + double distanceMax: 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 + }) : super( + timeStamp: timeStamp, + kind: kind, + device: device, + position: position, + delta: delta, + buttons: buttons, + down: false, + obscured: obscured, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt + ); +} + /// The pointer has made contact with the device. class PointerDownEvent extends PointerEvent { /// Creates a pointer down event. @@ -320,6 +376,7 @@ class PointerDownEvent extends PointerEvent { Duration timeStamp: Duration.ZERO, int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, Point position: Point.origin, int buttons: 0, bool obscured: false, @@ -337,6 +394,7 @@ class PointerDownEvent extends PointerEvent { timeStamp: timeStamp, pointer: pointer, kind: kind, + device: device, position: position, buttons: buttons, down: true, @@ -355,7 +413,13 @@ class PointerDownEvent extends PointerEvent { ); } -/// The pointer has moved with respect to the device. +/// The pointer has moved with respect to the device while the pointer is in +/// contact with the device. +/// +/// See also: +/// +/// * [PointerHoverEvent], which reports movement while the pointer is not in +/// contact with the device. class PointerMoveEvent extends PointerEvent { /// Creates a pointer move event. /// @@ -364,15 +428,14 @@ class PointerMoveEvent extends PointerEvent { Duration timeStamp: Duration.ZERO, int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, Point position: Point.origin, Offset delta: Offset.zero, int buttons: 0, - bool down: false, bool obscured: false, double pressure: 1.0, double pressureMin: 1.0, double pressureMax: 1.0, - double distance: 0.0, double distanceMax: 0.0, double radiusMajor: 0.0, double radiusMinor: 0.0, @@ -384,15 +447,16 @@ class PointerMoveEvent extends PointerEvent { timeStamp: timeStamp, pointer: pointer, kind: kind, + device: device, position: position, delta: delta, buttons: buttons, - down: down, + down: true, obscured: obscured, pressure: pressure, pressureMin: pressureMin, pressureMax: pressureMax, - distance: distance, + distance: 0.0, distanceMax: distanceMax, radiusMajor: radiusMajor, radiusMinor: radiusMinor, @@ -412,6 +476,7 @@ class PointerUpEvent extends PointerEvent { Duration timeStamp: Duration.ZERO, int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, Point position: Point.origin, int buttons: 0, bool obscured: false, @@ -427,8 +492,10 @@ class PointerUpEvent extends PointerEvent { timeStamp: timeStamp, pointer: pointer, kind: kind, + device: device, position: position, buttons: buttons, + down: false, obscured: obscured, pressureMin: pressureMin, pressureMax: pressureMax, @@ -450,6 +517,7 @@ class PointerCancelEvent extends PointerEvent { Duration timeStamp: Duration.ZERO, int pointer: 0, PointerDeviceKind kind: PointerDeviceKind.touch, + int device: 0, Point position: Point.origin, int buttons: 0, bool obscured: false, @@ -465,8 +533,10 @@ class PointerCancelEvent extends PointerEvent { timeStamp: timeStamp, pointer: pointer, kind: kind, + device: device, position: position, buttons: buttons, + down: false, obscured: obscured, pressureMin: pressureMin, pressureMax: pressureMax, diff --git a/packages/flutter/test/gestures/gesture_binding_test.dart b/packages/flutter/test/gestures/gesture_binding_test.dart index f6ff41b1270..171c4d0f8bb 100644 --- a/packages/flutter/test/gestures/gesture_binding_test.dart +++ b/packages/flutter/test/gestures/gesture_binding_test.dart @@ -34,7 +34,7 @@ void main() { test('Pointer tap events', () { ui.PointerDataPacket packet = new ui.PointerDataPacket( - pointers: [ + data: [ new ui.PointerData(change: ui.PointerChange.down), new ui.PointerData(change: ui.PointerChange.up), ] @@ -51,7 +51,7 @@ void main() { test('Pointer move events', () { ui.PointerDataPacket packet = new ui.PointerDataPacket( - pointers: [ + data: [ new ui.PointerData(change: ui.PointerChange.down), new ui.PointerData(change: ui.PointerChange.move), new ui.PointerData(change: ui.PointerChange.up), @@ -70,7 +70,7 @@ void main() { test('Synthetic move events', () { ui.PointerDataPacket packet = new ui.PointerDataPacket( - pointers: [ + data: [ new ui.PointerData( change: ui.PointerChange.down, physicalX: 1.0, @@ -97,7 +97,7 @@ void main() { test('Pointer cancel events', () { ui.PointerDataPacket packet = new ui.PointerDataPacket( - pointers: [ + data: [ new ui.PointerData(change: ui.PointerChange.down), new ui.PointerData(change: ui.PointerChange.cancel), ] @@ -114,7 +114,7 @@ void main() { test('Can cancel pointers', () { ui.PointerDataPacket packet = new ui.PointerDataPacket( - pointers: [ + data: [ new ui.PointerData(change: ui.PointerChange.down), new ui.PointerData(change: ui.PointerChange.up), ] diff --git a/packages/flutter_test/lib/src/test_pointer.dart b/packages/flutter_test/lib/src/test_pointer.dart index 39db81e94f5..083259cdeb0 100644 --- a/packages/flutter_test/lib/src/test_pointer.dart +++ b/packages/flutter_test/lib/src/test_pointer.dart @@ -66,7 +66,6 @@ class TestPointer { return new PointerMoveEvent( timeStamp: timeStamp, pointer: pointer, - down: _isDown, position: newLocation, delta: delta );