blob: f08ef76c0c9378ea530ce7fb976b95b804b9c881 [file] [log] [blame]
// 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.
// @dart = 2.6
part of engine;
class _PointerState {
_PointerState(this.x, this.y);
/// The identifier used in framework hit test.
int get pointer => _pointer;
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 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
/// 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 browser pointer identifiers to PointerEvent pointer identifiers.
final Map<int, _PointerState> _pointers = <int, _PointerState>{};
/// 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(int device, double x, double y) {
return _pointers.putIfAbsent(
device,
() => _PointerState(x, y),
);
}
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: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
pointerIdentifier: state.pointer ?? 0,
physicalX: physicalX,
physicalY: physicalY,
physicalDeltaX: deltaX,
physicalDeltaY: deltaY,
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(int device, double physicalX, double physicalY) {
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
return state.x != physicalX || state.y != 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: timeStamp,
change: change,
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: device,
pointerIdentifier: state.pointer ?? 0,
physicalX: physicalX,
physicalY: physicalY,
physicalDeltaX: deltaX,
physicalDeltaY: deltaY,
buttons: buttons,
obscured: obscured,
synthesized: true,
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,
);
}
/// Converts the given html pointer event metrics into a sequence of framework-compatible
/// pointer data and stores it into [result]
void convert(
List<ui.PointerData> 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);
state.startNewPointer();
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)) {
assert(alreadyAdded);
// Synthesize a hover of the pointer to the down location before
// sending the down event, if necessary.
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.hover,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: 0,
obscured: obscured,
pressure: 0.0,
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,
)
);
}
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);
// Cancel events can have different coordinates due to various
// reasons (window lost focus which is accompanied by window
// movement, or PointerEvent simply always gives 0). Instead of
// caring about the coordinates, we want to cancel the pointers as
// soon as possible.
if (change == ui.PointerChange.cancel) {
physicalX = state.x;
physicalY = state.y;
}
if (_locationHasChanged(device, physicalX, physicalY)) {
// Synthesize a move of the pointer to the up location before
// sending the up event, if necessary.
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,
)
);
}
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,
)
);
if (kind == ui.PointerDeviceKind.touch) {
// The browser sends a new device ID for each touch gesture. To
// avoid memory leaks, we send a "remove" event when the gesture is
// over (i.e. when "up" or "cancel" is received).
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.remove,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: 0,
obscured: obscured,
pressure: 0.0,
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,
)
);
_pointers.remove(device);
}
break;
case ui.PointerChange.remove:
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: state.x,
physicalY: state.y,
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,
)
);
_pointers.remove(device);
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,
)
);
}
}
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;
}
}
}
}