Commit a4a783b6 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add support for pointer hover (#6884)

parent 15fb5c4c
ce8b187914f599e8e579fab829671fb2f07064b7
d1bc4c4850ee155430b3ff66609a225364048257
......@@ -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
......
......@@ -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();
}
......
......@@ -36,6 +36,13 @@ class PointerEventConverter {
// Map from platform pointer identifiers to PointerEvent pointer identifiers.
static Map<int, _PointerState> _pointers = <int, _PointerState>{};
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<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) sync* {
for (ui.PointerData datum in data) {
......@@ -43,19 +50,36 @@ 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);
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
);
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);
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 +91,86 @@ class PointerEventConverter {
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
......
......@@ -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,
......
......@@ -34,7 +34,7 @@ void main() {
test('Pointer tap events', () {
ui.PointerDataPacket packet = new ui.PointerDataPacket(
pointers: <ui.PointerData>[
data: <ui.PointerData>[
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: <ui.PointerData>[
data: <ui.PointerData>[
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: <ui.PointerData>[
data: <ui.PointerData>[
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: <ui.PointerData>[
data: <ui.PointerData>[
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: <ui.PointerData>[
data: <ui.PointerData>[
new ui.PointerData(change: ui.PointerChange.down),
new ui.PointerData(change: ui.PointerChange.up),
]
......
......@@ -66,7 +66,6 @@ class TestPointer {
return new PointerMoveEvent(
timeStamp: timeStamp,
pointer: pointer,
down: _isDown,
position: newLocation,
delta: delta
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment