Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 57 additions & 131 deletions lib/web_ui/lib/src/engine/pointer_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -415,41 +415,43 @@ class _ButtonSanitizer {
);
}

_SanitizedDetails? sanitizeUpEvent() {
_SanitizedDetails? sanitizeMissingRightClickUp({required int buttons}) {
final int newPressedButtons = _htmlButtonsToFlutterButtons(buttons);
// This could happen when RMB is clicked and released but no pointerup
// event was received because context menu was shown.
if (_pressedButtons != 0 && newPressedButtons == 0) {
_pressedButtons = 0;
return _SanitizedDetails(
change: ui.PointerChange.up,
buttons: _pressedButtons,
);
}
return null;
}

_SanitizedDetails? sanitizeUpEvent({required int? buttons}) {
// 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? sanitizeUpEventWithButtons({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) {
_pressedButtons = _htmlButtonsToFlutterButtons(buttons ?? 0);

if (_pressedButtons == 0) {
// All buttons have been released.
return _SanitizedDetails(
change: ui.PointerChange.up,
buttons: _pressedButtons,
);
} else {
// There are still some unreleased buttons, we shouldn't send an up event
// yet. Instead we send a move event to update the position of the pointer.
return _SanitizedDetails(
change: ui.PointerChange.move,
buttons: _pressedButtons,
);
}

_pressedButtons = newPressedButtons;

return _SanitizedDetails(
change: _pressedButtons == 0
? ui.PointerChange.hover
: ui.PointerChange.move,
buttons: _pressedButtons,
);
}

_SanitizedDetails sanitizeCancelEvent() {
Expand All @@ -462,7 +464,6 @@ class _ButtonSanitizer {
}

typedef _PointerEventListener = dynamic Function(html.PointerEvent event);
const int kContextMenuButton = 2;

/// Adapter class to be used with browsers that support native pointer events.
///
Expand Down Expand Up @@ -512,54 +513,45 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
final int device = event.pointerId!;
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
if (event.button == kContextMenuButton) {
_handleMissingRightMouseUpEvent(sanitizer,
sanitizer._pressedButtons,
sanitizer._pressedButtons & ~kContextMenuButton,
event,
pointerData);
final _SanitizedDetails? up =
sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!);
if (up != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: up);
}
final _SanitizedDetails details =
final _SanitizedDetails down =
sanitizer.sanitizeDownEvent(
button: event.button,
buttons: event.buttons!,
);
_convertEventsToPointerData(data: pointerData, event: event, details: details);
_convertEventsToPointerData(data: pointerData, event: event, details: down);
_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 int buttonsBeforeEvent = sanitizer._pressedButtons;
final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).map(
(html.PointerEvent expandedEvent) {
return sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons!);
},
);
_handleMissingRightMouseUpEvent(
sanitizer,
buttonsBeforeEvent,
(sanitizer._inferDownFlutterButtons(event.button, event.buttons!)
& kContextMenuButton),
event,
pointerData);
for (_SanitizedDetails details in detailsList) {
_convertEventsToPointerData(data: pointerData, event: event, details: details);
final List<html.PointerEvent> expandedEvents = _expandEvents(event);
for (final html.PointerEvent event in expandedEvents) {
final _SanitizedDetails? up = sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!);
if (up != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: up);
}
final _SanitizedDetails move = sanitizer.sanitizeMoveEvent(buttons: event.buttons!);
_convertEventsToPointerData(data: pointerData, event: event, details: move);
}
_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();
final _SanitizedDetails? details = _getSanitizer(device).sanitizeUpEvent(buttons: event.buttons);
_removePointerIfUnhoverable(event);
if (details != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: details);
_callback(pointerData);
}
_callback(pointerData);
}, acceptOutsideGlasspane: true);

// A browser fires cancel event if it concludes the pointer will no longer
Expand All @@ -578,39 +570,6 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
});
}

// Handle special case where right mouse button no longer is pressed.
// We need to synthesize right mouse up, otherwise drag gesture will fail
// to complete or multiple RMB down events will lead to wrong state.
void _handleMissingRightMouseUpEvent(_ButtonSanitizer sanitizer,
int buttonsBeforeEvent, int buttonsAfterEvent, html.PointerEvent event,
List<ui.PointerData> pointerData) {
if ((buttonsBeforeEvent & kContextMenuButton) != 0 &&
buttonsAfterEvent == 0) {
final ui.PointerDeviceKind kind =
_pointerTypeToDeviceKind(event.pointerType!);
final int device = kind == ui.PointerDeviceKind.mouse
? _mouseDeviceId : event.pointerId!;
final double tilt = _computeHighestTilt(event);
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!);
sanitizer._pressedButtons &= ~kContextMenuButton;
_pointerDataConverter.convert(
pointerData,
change: ui.PointerChange.up,
timeStamp: timeStamp,
kind: kind,
signalKind: ui.PointerSignalKind.none,
device: device,
physicalX: event.client.x.toDouble() * ui.window.devicePixelRatio,
physicalY: event.client.y.toDouble() * ui.window.devicePixelRatio,
buttons: sanitizer._pressedButtons,
pressure: event.pressure as double,
pressureMin: 0.0,
pressureMax: 1.0,
tilt: tilt,
);
}
}

// For each event that is de-coalesced from `event` and described in
// `details`, convert it to pointer data and store in `data`.
void _convertEventsToPointerData({
Expand Down Expand Up @@ -852,12 +811,10 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin {
void setup() {
_addMouseEventListener('mousedown', (html.MouseEvent event) {
final List<ui.PointerData> pointerData = <ui.PointerData>[];
if (event.button == kContextMenuButton) {
_handleMissingRightMouseUpEvent(_sanitizer,
_sanitizer._pressedButtons,
_sanitizer._pressedButtons & ~kContextMenuButton,
event,
pointerData);
final _SanitizedDetails? up =
_sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!);
if (up != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: up);
}
final _SanitizedDetails sanitizedDetails =
_sanitizer.sanitizeDownEvent(
Expand All @@ -870,60 +827,29 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin {

_addMouseEventListener('mousemove', (html.MouseEvent event) {
final List<ui.PointerData> pointerData = <ui.PointerData>[];
final int buttonsBeforeEvent = _sanitizer._pressedButtons;
_handleMissingRightMouseUpEvent(
_sanitizer,
buttonsBeforeEvent,
(_sanitizer._inferDownFlutterButtons(event.button, event.buttons!)
& kContextMenuButton),
event,
pointerData);
final _SanitizedDetails sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons!);
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
final _SanitizedDetails? up = _sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!);
if (up != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: up);
}
final _SanitizedDetails move = _sanitizer.sanitizeMoveEvent(buttons: event.buttons!);
_convertEventsToPointerData(data: pointerData, event: event, details: move);
_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.sanitizeUpEventWithButtons(buttons: event.buttons!)!;
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
_callback(pointerData);
final _SanitizedDetails? sanitizedDetails = _sanitizer.sanitizeUpEvent(buttons: event.buttons);
if (sanitizedDetails != null) {
_convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails);
_callback(pointerData);
}
}, acceptOutsideGlasspane: true);

_addWheelEventListener((html.Event event) {
_handleWheelEvent(event);
});
}

// Handle special case where right mouse button no longer is pressed.
// We need to synthesize right mouse up, otherwise drag gesture will fail
// to complete or multiple RMB down events will lead to wrong state.
void _handleMissingRightMouseUpEvent(_ButtonSanitizer sanitizer,
int buttonsBeforeEvent, int buttonsAfterEvent, html.MouseEvent event,
List<ui.PointerData> pointerData) {
if ((buttonsBeforeEvent & kContextMenuButton) != 0 &&
buttonsAfterEvent == 0) {
sanitizer._pressedButtons &= ~2;
_pointerDataConverter.convert(
pointerData,
change: ui.PointerChange.up,
timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp!),
kind: ui.PointerDeviceKind.mouse,
signalKind: ui.PointerSignalKind.none,
device: _mouseDeviceId,
physicalX: event.client.x.toDouble() * ui.window.devicePixelRatio,
physicalY: event.client.y.toDouble() * ui.window.devicePixelRatio,
buttons: _sanitizer._pressedButtons,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
);
}
}

// For each event that is de-coalesced from `event` and described in
// `detailsList`, convert it to pointer data and store in `data`.
void _convertEventsToPointerData({
Expand Down
26 changes: 10 additions & 16 deletions lib/web_ui/lib/src/engine/pointer_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ class _PointerState {
_pointer = _pointerCount;
}

bool down = false;

double x;
double y;
}
Expand Down Expand Up @@ -240,8 +238,9 @@ class PointerDataConverter {
double scrollDeltaY = 0.0,
}) {
if (_debugLogPointerConverter) {
print('>> device=$device change = $change buttons = $buttons');
print('>> device=$device change=$change buttons=$buttons');
}
final bool isDown = buttons != 0;
assert(change != null); // ignore: unnecessary_null_comparison
if (signalKind == null ||
signalKind == ui.PointerSignalKind.none) {
Expand Down Expand Up @@ -281,9 +280,8 @@ class PointerDataConverter {
break;
case ui.PointerChange.hover:
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
assert(!state.down);
_ensureStateForPointer(device, physicalX, physicalY);
assert(!isDown);
if (!alreadyAdded) {
// Synthesizes an add pointer data.
result.add(
Expand Down Expand Up @@ -348,7 +346,7 @@ class PointerDataConverter {
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
assert(!state.down);
assert(isDown);
state.startNewPointer();
if (!alreadyAdded) {
// Synthesizes an add pointer data.
Expand Down Expand Up @@ -412,7 +410,6 @@ class PointerDataConverter {
)
);
}
state.down = true;
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
Expand Down Expand Up @@ -445,8 +442,7 @@ class PointerDataConverter {
break;
case ui.PointerChange.move:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device]!;
assert(state.down);
assert(isDown);
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
Expand Down Expand Up @@ -481,7 +477,7 @@ class PointerDataConverter {
case ui.PointerChange.cancel:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device]!;
assert(state.down);
assert(!isDown);
// 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
Expand Down Expand Up @@ -522,7 +518,6 @@ class PointerDataConverter {
)
);
}
state.down = false;
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
Expand Down Expand Up @@ -588,7 +583,7 @@ class PointerDataConverter {
case ui.PointerChange.remove:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device]!;
assert(!state.down);
assert(!isDown);
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
Expand Down Expand Up @@ -624,8 +619,7 @@ class PointerDataConverter {
switch (signalKind) {
case ui.PointerSignalKind.scroll:
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
_ensureStateForPointer(device, physicalX, physicalY);
if (!alreadyAdded) {
// Synthesizes an add pointer data.
result.add(
Expand Down Expand Up @@ -661,7 +655,7 @@ class PointerDataConverter {
// 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) {
if (isDown) {
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
Expand Down
Loading