blob: 48e43dc72db4516858942cf1220ad41d0373b64b [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;
/// Set this flag to true to see all the fired events in the console.
const bool _debugLogPointerEvents = false;
/// The signature of a callback that handles pointer events.
typedef _PointerDataCallback = void Function(Iterable<ui.PointerData>);
// The mask for the bitfield of event buttons. Buttons not contained in this
// mask are cut off.
//
// In Flutter we used `kMaxUnsignedSMI`, but since that value is not available
// here, we use an already very large number (30 bits).
const int _kButtonsMask = 0x3FFFFFFF;
// Intentionally set to -1 so it doesn't conflict with other device IDs.
const int _mouseDeviceId = -1;
const int _kPrimaryMouseButton = 0x1;
const int _kSecondaryMouseButton = 0x2;
const int _kMiddleMouseButton =0x4;
int _nthButton(int n) => 0x1 << n;
/// Convert the `button` property of PointerEvent or MouseEvent to a bit mask of
/// its `buttons` property.
///
/// The `button` property is a integer describing the button changed in an event,
/// which is sequentially 0 for LMB, 1 for MMB, 2 for RMB, 3 for backward and
/// 4 for forward, etc.
///
/// The `buttons` property is a bitfield describing the buttons pressed after an
/// event, which is 0x1 for LMB, 0x4 for MMB, 0x2 for RMB, 0x8 for backward
/// and 0x10 for forward, etc.
@visibleForTesting
int convertButtonToButtons(int button) {
assert(button >= 0, 'Unexpected negative button $button.');
switch(button) {
case 0:
return _kPrimaryMouseButton;
case 1:
return _kMiddleMouseButton;
case 2:
return _kSecondaryMouseButton;
default:
return _nthButton(button);
}
}
class PointerBinding {
/// The singleton instance of this object.
static PointerBinding get instance => _instance;
static PointerBinding _instance;
static void initInstance(html.Element glassPaneElement) {
if (_instance == null) {
_instance = PointerBinding._(glassPaneElement);
assert(() {
registerHotRestartListener(() {
_instance._adapter?.clearListeners();
_instance._pointerDataConverter?.clearPointerState();
});
return true;
}());
}
}
PointerBinding._(this.glassPaneElement) {
_pointerDataConverter = PointerDataConverter();
_detector = const PointerSupportDetector();
_adapter = _createAdapter();
}
final html.Element glassPaneElement;
PointerSupportDetector _detector;
_BaseAdapter _adapter;
PointerDataConverter _pointerDataConverter;
/// Should be used in tests to define custom detection of pointer support.
///
/// ```dart
/// // Forces PointerBinding to use mouse events.
/// class MyTestDetector extends PointerSupportDetector {
/// @override
/// final bool hasPointerEvents = false;
///
/// @override
/// final bool hasTouchEvents = false;
///
/// @override
/// final bool hasMouseEvents = true;
/// }
///
/// PointerBinding.instance.debugOverrideDetector(MyTestDetector());
/// ```
void debugOverrideDetector(PointerSupportDetector newDetector) {
newDetector ??= const PointerSupportDetector();
// When changing the detector, we need to swap the adapter.
if (newDetector != _detector) {
_detector = newDetector;
_adapter?.clearListeners();
_adapter = _createAdapter();
_pointerDataConverter?.clearPointerState();
}
}
_BaseAdapter _createAdapter() {
if (_detector.hasPointerEvents) {
return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter);
}
if (_detector.hasTouchEvents) {
return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter);
}
if (_detector.hasMouseEvents) {
return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter);
}
return null;
}
void _onPointerData(Iterable<ui.PointerData> data) {
final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList());
if (window._onPointerDataPacket != null) {
window.invokeOnPointerDataPacket(packet);
}
}
}
class PointerSupportDetector {
const PointerSupportDetector();
bool get hasPointerEvents => js_util.hasProperty(html.window, 'PointerEvent');
bool get hasTouchEvents => js_util.hasProperty(html.window, 'TouchEvent');
bool get hasMouseEvents => js_util.hasProperty(html.window, 'MouseEvent');
@override
String toString() =>
'pointers:$hasPointerEvents, touch:$hasTouchEvents, mouse:$hasMouseEvents';
}
/// Common functionality that's shared among adapters.
abstract class _BaseAdapter {
_BaseAdapter(this._callback, this.glassPaneElement, this._pointerDataConverter) {
setup();
}
/// Listeners that are registered through dart to js api.
static final Map<String, html.EventListener> _listeners =
<String, html.EventListener>{};
/// Listeners that are registered through native javascript api.
static final Map<String, html.EventListener> _nativeListeners =
<String, html.EventListener>{};
final html.Element glassPaneElement;
_PointerDataCallback _callback;
PointerDataConverter _pointerDataConverter;
/// Each subclass is expected to override this method to attach its own event
/// listeners and convert events into pointer events.
void setup();
/// Remove all active event listeners.
void clearListeners() {
_listeners.forEach((String eventName, html.EventListener listener) {
html.window.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(
glassPaneElement,
'removeEventListener', <dynamic>[
'wheel',
listener,
]
);
});
_listeners.clear();
_nativeListeners.clear();
}
/// Adds a listener to the given [eventName].
///
/// The event listener is attached to [html.window] but only events that have
/// [glassPaneElement] as a target will be let through by default.
///
/// If [acceptOutsideGlasspane] is set to true, events outside of the
/// glasspane will also invoke the [handler].
void addEventListener(
String eventName,
html.EventListener handler, {
bool acceptOutsideGlasspane = false,
}) {
final html.EventListener loggedHandler = (html.Event event) {
if (!acceptOutsideGlasspane && !glassPaneElement.contains(event.target)) {
return;
}
if (_debugLogPointerEvents) {
print(event.type);
}
// Report the event to semantics. This information is used to debounce
// browser gestures. Semantics tells us whether it is safe to forward
// the event to the framework.
if (EngineSemanticsOwner.instance.receiveGlobalEvent(event)) {
handler(event);
}
};
_listeners[eventName] = loggedHandler;
// We have to attach the event listener on the window instead of the
// glasspane element. That's because "up" events that occur outside the
// browser are only reported on window, not on DOM elements.
// See: https://github.com/flutter/flutter/issues/52827
html.window.addEventListener(eventName, loggedHandler, true);
}
/// Converts a floating number timestamp (in milliseconds) to a [Duration] by
/// splitting it into two integer components: milliseconds + microseconds.
static Duration _eventTimeStampToDuration(num milliseconds) {
final int ms = milliseconds.toInt();
final int micro =
((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
return Duration(milliseconds: ms, microseconds: micro);
}
}
mixin _WheelEventListenerMixin on _BaseAdapter {
List<ui.PointerData> _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<ui.PointerData> data = <ui.PointerData>[];
_pointerDataConverter.convert(
data,
change: ui.PointerChange.hover,
timeStamp: _BaseAdapter._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));
_BaseAdapter._nativeListeners['wheel'] = jsHandler;
js_util.setProperty(eventOptions, 'passive', false);
js_util.callMethod(
glassPaneElement,
'addEventListener', <dynamic>[
'wheel',
jsHandler,
eventOptions
]
);
}
}
@immutable
class _SanitizedDetails {
const _SanitizedDetails({
@required this.buttons,
@required this.change,
});
final ui.PointerChange change;
final int buttons;
@override
String toString() => '$runtimeType(change: $change, buttons: $buttons)';
}
class _ButtonSanitizer {
int _pressedButtons = 0;
/// Transform [html.PointerEvent.buttons] to Flutter's PointerEvent buttons.
int _htmlButtonsToFlutterButtons(int buttons) {
// Flutter's button definition conveniently matches that of JavaScript
// from primary button (0x1) to forward button (0x10), which allows us to
// avoid transforming it bit by bit.
return buttons & _kButtonsMask;
}
/// Given [html.PointerEvent.button] and [html.PointerEvent.buttons], tries to
/// infer the correct value for Flutter buttons.
int _inferDownFlutterButtons(int button, int buttons) {
if (buttons == 0 && button > -1) {
// In some cases, the browser sends `buttons:0` in a down event. In such
// case, we try to infer the value from `button`.
buttons = convertButtonToButtons(button);
}
return _htmlButtonsToFlutterButtons(buttons);
}
_SanitizedDetails sanitizeDownEvent({
@required int button,
@required int buttons,
}) {
// If the pointer is already down, we just send a move event with the new
// `buttons` value.
if (_pressedButtons != 0) {
return sanitizeMoveEvent(buttons: buttons);
}
_pressedButtons = _inferDownFlutterButtons(button, buttons);
return _SanitizedDetails(
change: ui.PointerChange.down,
buttons: _pressedButtons,
);
}
_SanitizedDetails sanitizeMoveEvent({@required int buttons}) {
final int newPressedButtons = _htmlButtonsToFlutterButtons(buttons);
// This could happen when the context menu is active and the user clicks
// RMB somewhere else. The browser sends a down event with `buttons:0`.
//
// In this case, we keep the old `buttons` value so we don't confuse the
// framework.
if (_pressedButtons != 0 && newPressedButtons == 0) {
return _SanitizedDetails(
change: ui.PointerChange.move,
buttons: _pressedButtons,
);
}
// This could happen when the user clicks RMB then moves the mouse quickly.
// The brower sends a move event with `buttons:2` even though there's no
// buttons down yet.
if (_pressedButtons == 0 && newPressedButtons != 0) {
return _SanitizedDetails(
change: ui.PointerChange.hover,
buttons: _pressedButtons,
);
}
_pressedButtons = newPressedButtons;
return _SanitizedDetails(
change: _pressedButtons == 0
? ui.PointerChange.hover
: ui.PointerChange.move,
buttons: _pressedButtons,
);
}
_SanitizedDetails sanitizeUpEvent() {
// The pointer could have been released by a `pointerout` event, in which
// case `pointerup` should have no effect.
if (_pressedButtons == 0) {
return null;
}
_pressedButtons = 0;
return _SanitizedDetails(
change: ui.PointerChange.up,
buttons: _pressedButtons,
);
}
_SanitizedDetails sanitizeCancelEvent() {
_pressedButtons = 0;
return _SanitizedDetails(
change: ui.PointerChange.cancel,
buttons: _pressedButtons,
);
}
}
typedef _PointerEventListener = dynamic Function(html.PointerEvent event);
/// Adapter class to be used with browsers that support native pointer events.
///
/// For the difference between MouseEvent and PointerEvent, see _MouseAdapter.
class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
_PointerAdapter(
_PointerDataCallback callback,
html.Element glassPaneElement,
PointerDataConverter _pointerDataConverter
) : super(callback, glassPaneElement, _pointerDataConverter);
final Map<int, _ButtonSanitizer> _sanitizers = <int, _ButtonSanitizer>{};
@visibleForTesting
Iterable<int> debugTrackedDevices() => _sanitizers.keys;
_ButtonSanitizer _ensureSanitizer(int device) {
return _sanitizers.putIfAbsent(device, () => _ButtonSanitizer());
}
_ButtonSanitizer _getSanitizer(int device) {
final _ButtonSanitizer sanitizer = _sanitizers[device];
assert(sanitizer != null);
return sanitizer;
}
void _removePointerIfUnhoverable(html.PointerEvent event) {
if (event.pointerType == 'touch') {
_sanitizers.remove(event.pointerId);
}
}
void _addPointerEventListener(
String eventName,
_PointerEventListener handler, {
bool acceptOutsideGlasspane = false,
}) {
addEventListener(eventName, (html.Event event) {
final html.PointerEvent pointerEvent = event;
return handler(pointerEvent);
}, acceptOutsideGlasspane: acceptOutsideGlasspane);
}
@override
void setup() {
_addPointerEventListener('pointerdown', (html.PointerEvent event) {
final int device = event.pointerId;
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _SanitizedDetails details =
_ensureSanitizer(device).sanitizeDownEvent(
button: event.button,
buttons: event.buttons,
);
_convertEventsToPointerData(data: pointerData, event: event, details: details);
_callback(pointerData);
});
_addPointerEventListener('pointermove', (html.PointerEvent event) {
final int device = event.pointerId;
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).map(
(html.PointerEvent expandedEvent) => sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons),
);
for (_SanitizedDetails details in detailsList) {
_convertEventsToPointerData(data: pointerData, event: event, details: details);
}
_callback(pointerData);
}, acceptOutsideGlasspane: true);
_addPointerEventListener('pointerup', (html.PointerEvent event) {
final int device = event.pointerId;
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _SanitizedDetails details = _getSanitizer(device).sanitizeUpEvent();
_removePointerIfUnhoverable(event);
if (details != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: details);
}
_callback(pointerData);
}, acceptOutsideGlasspane: true);
// A browser fires cancel event if it concludes the pointer will no longer
// be able to generate events (example: device is deactivated)
_addPointerEventListener('pointercancel', (html.PointerEvent event) {
final int device = event.pointerId;
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _SanitizedDetails details = _getSanitizer(device).sanitizeCancelEvent();
_removePointerIfUnhoverable(event);
_convertEventsToPointerData(data: pointerData, event: event, details: details);
_callback(pointerData);
});
_addWheelEventListener((html.Event event) {
assert(event is html.WheelEvent);
if (_debugLogPointerEvents) {
print(event.type);
}
_callback(_convertWheelEventToPointerData(event));
// Prevent default so mouse wheel event doesn't get converted to
// a scroll event that semantic nodes would process.
event.preventDefault();
});
}
// For each event that is de-coalesced from `event` and described in
// `details`, convert it to pointer data and store in `data`.
void _convertEventsToPointerData({
@required List<ui.PointerData> data,
@required html.PointerEvent event,
@required _SanitizedDetails details,
}) {
assert(data != null);
assert(event != null);
assert(details != null);
final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType);
// We force `device: _mouseDeviceId` on mouse pointers because Wheel events
// might come before any PointerEvents, and since wheel events don't contain
// pointerId we always assign `device: _mouseDeviceId` to them.
final int device = kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : event.pointerId;
final double tilt = _computeHighestTilt(event);
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp);
_pointerDataConverter.convert(
data,
change: details.change,
timeStamp: timeStamp,
kind: kind,
signalKind: ui.PointerSignalKind.none,
device: device,
physicalX: event.client.x * ui.window.devicePixelRatio,
physicalY: event.client.y * ui.window.devicePixelRatio,
buttons: details.buttons,
pressure: event.pressure,
pressureMin: 0.0,
pressureMax: 1.0,
tilt: tilt,
);
}
List<html.PointerEvent> _expandEvents(html.PointerEvent event) {
// For browsers that don't support `getCoalescedEvents`, we fallback to
// using the original event.
if (js_util.hasProperty(event, 'getCoalescedEvents')) {
final List<html.PointerEvent> coalescedEvents =
event.getCoalescedEvents().cast<html.PointerEvent>();
// Some events don't perform coalescing, so they return an empty list. In
// that case, we also fallback to using the original event.
if (coalescedEvents.isNotEmpty) {
return coalescedEvents;
}
}
return <html.PointerEvent>[event];
}
ui.PointerDeviceKind _pointerTypeToDeviceKind(String pointerType) {
switch (pointerType) {
case 'mouse':
return ui.PointerDeviceKind.mouse;
case 'pen':
return ui.PointerDeviceKind.stylus;
case 'touch':
return ui.PointerDeviceKind.touch;
default:
return ui.PointerDeviceKind.unknown;
}
}
/// Tilt angle is -90 to + 90. Take maximum deflection and convert to radians.
double _computeHighestTilt(html.PointerEvent e) =>
(e.tiltX.abs() > e.tiltY.abs() ? e.tiltX : e.tiltY).toDouble() /
180.0 *
math.pi;
}
typedef _TouchEventListener = dynamic Function(html.TouchEvent event);
/// Adapter to be used with browsers that support touch events.
class _TouchAdapter extends _BaseAdapter {
_TouchAdapter(
_PointerDataCallback callback,
html.Element glassPaneElement,
PointerDataConverter _pointerDataConverter
) : super(callback, glassPaneElement, _pointerDataConverter);
final Set<int> _pressedTouches = <int>{};
bool _isTouchPressed(int identifier) => _pressedTouches.contains(identifier);
void _pressTouch(int identifier) { _pressedTouches.add(identifier); }
void _unpressTouch(int identifier) { _pressedTouches.remove(identifier); }
void _addTouchEventListener(String eventName, _TouchEventListener handler) {
addEventListener(eventName, (html.Event event) {
final html.TouchEvent touchEvent = event;
return handler(touchEvent);
});
}
@override
void setup() {
_addTouchEventListener('touchstart', (html.TouchEvent event) {
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp);
final List<ui.PointerData> pointerData = <ui.PointerData>[];
for (html.Touch touch in event.changedTouches) {
final nowPressed = _isTouchPressed(touch.identifier);
if (!nowPressed) {
_pressTouch(touch.identifier);
_convertEventToPointerData(
data: pointerData,
change: ui.PointerChange.down,
touch: touch,
pressed: true,
timeStamp: timeStamp,
);
}
}
_callback(pointerData);
});
_addTouchEventListener('touchmove', (html.TouchEvent event) {
event.preventDefault(); // Prevents standard overscroll on iOS/Webkit.
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp);
final List<ui.PointerData> pointerData = <ui.PointerData>[];
for (html.Touch touch in event.changedTouches) {
final nowPressed = _isTouchPressed(touch.identifier);
if (nowPressed) {
_convertEventToPointerData(
data: pointerData,
change: ui.PointerChange.move,
touch: touch,
pressed: true,
timeStamp: timeStamp,
);
}
}
_callback(pointerData);
});
_addTouchEventListener('touchend', (html.TouchEvent event) {
// On Safari Mobile, the keyboard does not show unless this line is
// added.
event.preventDefault();
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp);
final List<ui.PointerData> pointerData = <ui.PointerData>[];
for (html.Touch touch in event.changedTouches) {
final nowPressed = _isTouchPressed(touch.identifier);
if (nowPressed) {
_unpressTouch(touch.identifier);
_convertEventToPointerData(
data: pointerData,
change: ui.PointerChange.up,
touch: touch,
pressed: false,
timeStamp: timeStamp,
);
}
}
_callback(pointerData);
});
_addTouchEventListener('touchcancel', (html.TouchEvent event) {
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp);
final List<ui.PointerData> pointerData = <ui.PointerData>[];
for (html.Touch touch in event.changedTouches) {
final nowPressed = _isTouchPressed(touch.identifier);
if (nowPressed) {
_unpressTouch(touch.identifier);
_convertEventToPointerData(
data: pointerData,
change: ui.PointerChange.cancel,
touch: touch,
pressed: false,
timeStamp: timeStamp,
);
}
}
_callback(pointerData);
});
}
void _convertEventToPointerData({
@required List<ui.PointerData> data,
@required ui.PointerChange change,
@required html.Touch touch,
@required bool pressed,
@required Duration timeStamp,
}) {
_pointerDataConverter.convert(
data,
change: change,
timeStamp: timeStamp,
kind: ui.PointerDeviceKind.touch,
signalKind: ui.PointerSignalKind.none,
device: touch.identifier,
physicalX: touch.client.x * ui.window.devicePixelRatio,
physicalY: touch.client.y * ui.window.devicePixelRatio,
buttons: pressed ? _kPrimaryMouseButton : 0,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
);
}
}
typedef _MouseEventListener = dynamic Function(html.MouseEvent event);
/// Adapter to be used with browsers that support mouse events.
///
/// The difference between MouseEvent and PointerEvent can be illustrated using
/// a scenario of changing buttons during a drag sequence: LMB down, RMB down,
/// move, LMB up, RMB up, hover.
///
/// LMB down RMB down move LMB up RMB up hover
/// PntEvt type | pointerdown pointermove pointermove pointermove pointerup pointermove
/// button | 0 2 -1 0 2 -1
/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0
/// MosEvt type | mousedown mousedown mousemove mouseup mouseup mousemove
/// button | 0 2 0 0 2 0
/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0
///
/// The major differences are:
///
/// * The type of events for changing buttons during a drag sequence.
/// * The `button` for dragging or hovering.
class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin {
_MouseAdapter(
_PointerDataCallback callback,
html.Element glassPaneElement,
PointerDataConverter _pointerDataConverter
) : super(callback, glassPaneElement, _pointerDataConverter);
final _ButtonSanitizer _sanitizer = _ButtonSanitizer();
void _addMouseEventListener(
String eventName,
_MouseEventListener handler, {
bool acceptOutsideGlasspane = false,
}) {
addEventListener(eventName, (html.Event event) {
final html.MouseEvent mouseEvent = event;
return handler(mouseEvent);
}, acceptOutsideGlasspane: acceptOutsideGlasspane);
}
@override
void setup() {
_addMouseEventListener('mousedown', (html.MouseEvent event) {
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _SanitizedDetails sanitizedDetails =
_sanitizer.sanitizeDownEvent(
button: event.button,
buttons: event.buttons,
);
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
_callback(pointerData);
});
_addMouseEventListener('mousemove', (html.MouseEvent event) {
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _SanitizedDetails sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons);
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
_callback(pointerData);
}, acceptOutsideGlasspane: true);
_addMouseEventListener('mouseup', (html.MouseEvent event) {
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final bool isEndOfDrag = event.buttons == 0;
final _SanitizedDetails sanitizedDetails = isEndOfDrag ?
_sanitizer.sanitizeUpEvent() :
_sanitizer.sanitizeMoveEvent(buttons: event.buttons);
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
_callback(pointerData);
}, acceptOutsideGlasspane: true);
_addWheelEventListener((html.Event event) {
assert(event is html.WheelEvent);
if (_debugLogPointerEvents) {
print(event.type);
}
_callback(_convertWheelEventToPointerData(event));
// Prevent default so mouse wheel event doesn't get converted to
// a scroll event that semantic nodes would process.
event.preventDefault();
});
}
// For each event that is de-coalesced from `event` and described in
// `detailsList`, convert it to pointer data and store in `data`.
void _convertEventsToPointerData({
@required List<ui.PointerData> data,
@required html.MouseEvent event,
@required _SanitizedDetails details,
}) {
assert(data != null);
assert(event != null);
assert(details != null);
_pointerDataConverter.convert(
data,
change: details.change,
timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp),
kind: ui.PointerDeviceKind.mouse,
signalKind: ui.PointerSignalKind.none,
device: _mouseDeviceId,
physicalX: event.client.x * ui.window.devicePixelRatio,
physicalY: event.client.y * ui.window.devicePixelRatio,
buttons: details.buttons,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
);
}
}