Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9bba610

Browse files
committed
Refactor to handle bad luck in accelerated mouse deltas.
Basically, bias towards choosing mouse, but if timestamps are available, we can check the previous event and ensure that false-mouses are avoided.
1 parent 59bb9f9 commit 9bba610

File tree

2 files changed

+93
-23
lines changed

2 files changed

+93
-23
lines changed

lib/web_ui/lib/src/engine/pointer_binding.dart

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
349349
return (wheelDelta - (-3 * delta)).abs() > 1;
350350
}
351351

352-
bool _isTrackpadEvent(DomWheelEvent event, DomWheelEvent? lastWheelEvent) {
352+
bool _isTrackpadEvent(DomWheelEvent event, DomWheelEvent? lastEvent) {
353353
// This function relies on deprecated and non-standard implementation
354354
// details. Useful reference material can be found below.
355355
//
@@ -364,25 +364,38 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
364364
// wheel events.
365365
return false;
366366
}
367-
if ((event.deltaX % 120 == 0) && (event.deltaY % 120 == 0)) {
367+
if (_isAcceleratedMouseWheelDelta(event.deltaX, event.wheelDeltaX) ||
368+
_isAcceleratedMouseWheelDelta(event.deltaY, event.wheelDeltaY)) {
369+
return false;
370+
}
371+
if (((event.deltaX % 120 == 0) && (event.deltaY % 120 == 0)) ||
372+
(((event.wheelDeltaX ?? 1) % 240 == 0) && ((event.wheelDeltaY ?? 1) % 240) == 0)) {
368373
// While not in any formal web standard, `blink` and `webkit` browsers use
369374
// a delta of 120 to represent one mouse wheel turn. If both dimensions of
370375
// the delta are divisible by 120, this event is probably from a mouse.
371-
final num deltaXChange = (event.deltaX - (lastWheelEvent?.deltaX ?? 0)).abs();
372-
final num deltaYChange = (event.deltaY - (lastWheelEvent?.deltaY ?? 0)).abs();
373-
if ((lastWheelEvent == null) ||
376+
// Checking if wheelDeltaX and wheelDeltaY are both divisible by 240
377+
// catches any macOS accelerated mouse wheel deltas which by random chance
378+
// are not caught by _isAcceleratedMouseWheelDelta.
379+
final num deltaXChange = (event.deltaX - (lastEvent?.deltaX ?? 0)).abs();
380+
final num deltaYChange = (event.deltaY - (lastEvent?.deltaY ?? 0)).abs();
381+
if ((lastEvent == null) ||
374382
(deltaXChange == 0 && deltaYChange == 0) ||
375-
!(deltaXChange < 10 && deltaYChange < 10)) {
383+
!(deltaXChange < 20 && deltaYChange < 20)) {
376384
// A trackpad event might by chance have a delta of exactly 120, so
377385
// make sure this event does not have a similar delta to the previous
378386
// one before calling it a mouse event.
387+
if (event.timeStamp != null && lastEvent?.timeStamp != null) {
388+
// If the event has a large delta to the previous event, check if
389+
// it was preceded within 50 milliseconds by a trackpad event. This
390+
// handles unlucky 120-delta trackpad events during rapid movement.
391+
final num diffMs = event.timeStamp! - lastEvent!.timeStamp!;
392+
if (diffMs < 50 && _isTrackpadEvent(lastEvent, null)) {
393+
return true;
394+
}
395+
}
379396
return false;
380397
}
381398
}
382-
if (_isAcceleratedMouseWheelDelta(event.deltaX, event.wheelDeltaX) ||
383-
_isAcceleratedMouseWheelDelta(event.deltaY, event.wheelDeltaY)) {
384-
return false;
385-
}
386399
return true;
387400
}
388401

lib/web_ui/test/engine/pointer_binding_test.dart

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,28 @@ void testMain() {
11851185
wheelDeltaY: -360,
11861186
));
11871187

1188+
glassPane.dispatchEvent(context.wheel(
1189+
buttons: 0,
1190+
clientX: 10,
1191+
clientY: 10,
1192+
deltaX: 119,
1193+
deltaY: 119,
1194+
wheelDeltaX: -357,
1195+
wheelDeltaY: -357,
1196+
timeStamp: 10,
1197+
));
1198+
1199+
glassPane.dispatchEvent(context.wheel(
1200+
buttons: 0,
1201+
clientX: 10,
1202+
clientY: 10,
1203+
deltaX: -120,
1204+
deltaY: -120,
1205+
wheelDeltaX: 360,
1206+
wheelDeltaY: 360,
1207+
timeStamp: 20,
1208+
));
1209+
11881210
glassPane.dispatchEvent(context.wheel(
11891211
buttons: 0,
11901212
clientX: 10,
@@ -1205,7 +1227,7 @@ void testMain() {
12051227
wheelDeltaY: -360,
12061228
));
12071229

1208-
expect(packets, hasLength(4));
1230+
expect(packets, hasLength(6));
12091231

12101232
// An add will be synthesized.
12111233
expect(packets[0].data, hasLength(2));
@@ -1248,39 +1270,72 @@ void testMain() {
12481270
expect(packets[1].data[0].scrollDeltaX, equals(120.0));
12491271
expect(packets[1].data[0].scrollDeltaY, equals(120.0));
12501272

1251-
// Because the delta is in increments of 120, and is not similar to
1252-
// the previous event, it will be a mouse event.
1253-
expect(packets[2].data, hasLength(1));
1273+
// Because the delta is not in increments of 120 and has matching wheelDelta,
1274+
// it will be a trackpad event.
12541275
expect(packets[2].data[0].change, equals(ui.PointerChange.hover));
12551276
expect(
12561277
packets[2].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
12571278
expect(
1258-
packets[2].data[0].kind, equals(ui.PointerDeviceKind.mouse));
1279+
packets[2].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
12591280
expect(packets[2].data[0].pointerIdentifier, equals(0));
12601281
expect(packets[2].data[0].synthesized, isFalse);
12611282
expect(packets[2].data[0].physicalX, equals(10.0 * dpi));
12621283
expect(packets[2].data[0].physicalY, equals(10.0 * dpi));
12631284
expect(packets[2].data[0].physicalDeltaX, equals(0.0));
12641285
expect(packets[2].data[0].physicalDeltaY, equals(0.0));
1265-
expect(packets[2].data[0].scrollDeltaX, equals(0.0));
1266-
expect(packets[2].data[0].scrollDeltaY, equals(-120.0));
1286+
expect(packets[2].data[0].scrollDeltaX, equals(119.0));
1287+
expect(packets[2].data[0].scrollDeltaY, equals(119.0));
12671288

1268-
// Because the delta is not in increments of 120 and has non-matching
1269-
// wheelDelta, it will be a mouse event.
1270-
expect(packets[3].data, hasLength(1));
1289+
// Because the delta is in increments of 120, and is not similar to the
1290+
// previous event, but occured soon after the previous event, it will be
1291+
// a trackpad event.
12711292
expect(packets[3].data[0].change, equals(ui.PointerChange.hover));
12721293
expect(
12731294
packets[3].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
12741295
expect(
1275-
packets[3].data[0].kind, equals(ui.PointerDeviceKind.mouse));
1296+
packets[3].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
12761297
expect(packets[3].data[0].pointerIdentifier, equals(0));
12771298
expect(packets[3].data[0].synthesized, isFalse);
12781299
expect(packets[3].data[0].physicalX, equals(10.0 * dpi));
12791300
expect(packets[3].data[0].physicalY, equals(10.0 * dpi));
12801301
expect(packets[3].data[0].physicalDeltaX, equals(0.0));
12811302
expect(packets[3].data[0].physicalDeltaY, equals(0.0));
1282-
expect(packets[3].data[0].scrollDeltaX, equals(0.0));
1283-
expect(packets[3].data[0].scrollDeltaY, equals(40.0));
1303+
expect(packets[3].data[0].scrollDeltaX, equals(-120.0));
1304+
expect(packets[3].data[0].scrollDeltaY, equals(-120.0));
1305+
1306+
// Because the delta is in increments of 120, and is not similar to
1307+
// the previous event, and does not have a timestamp, it will be a mouse event.
1308+
expect(packets[4].data, hasLength(1));
1309+
expect(packets[4].data[0].change, equals(ui.PointerChange.hover));
1310+
expect(
1311+
packets[4].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
1312+
expect(
1313+
packets[4].data[0].kind, equals(ui.PointerDeviceKind.mouse));
1314+
expect(packets[4].data[0].pointerIdentifier, equals(0));
1315+
expect(packets[4].data[0].synthesized, isFalse);
1316+
expect(packets[4].data[0].physicalX, equals(10.0 * dpi));
1317+
expect(packets[4].data[0].physicalY, equals(10.0 * dpi));
1318+
expect(packets[4].data[0].physicalDeltaX, equals(0.0));
1319+
expect(packets[4].data[0].physicalDeltaY, equals(0.0));
1320+
expect(packets[4].data[0].scrollDeltaX, equals(0.0));
1321+
expect(packets[4].data[0].scrollDeltaY, equals(-120.0));
1322+
1323+
// Because the delta is not in increments of 120 and has non-matching
1324+
// wheelDelta, it will be a mouse event.
1325+
expect(packets[5].data, hasLength(1));
1326+
expect(packets[5].data[0].change, equals(ui.PointerChange.hover));
1327+
expect(
1328+
packets[5].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
1329+
expect(
1330+
packets[5].data[0].kind, equals(ui.PointerDeviceKind.mouse));
1331+
expect(packets[5].data[0].pointerIdentifier, equals(0));
1332+
expect(packets[5].data[0].synthesized, isFalse);
1333+
expect(packets[5].data[0].physicalX, equals(10.0 * dpi));
1334+
expect(packets[5].data[0].physicalY, equals(10.0 * dpi));
1335+
expect(packets[5].data[0].physicalDeltaX, equals(0.0));
1336+
expect(packets[5].data[0].physicalDeltaY, equals(0.0));
1337+
expect(packets[5].data[0].scrollDeltaX, equals(0.0));
1338+
expect(packets[5].data[0].scrollDeltaY, equals(40.0));
12841339
},
12851340
);
12861341

@@ -2993,6 +3048,7 @@ mixin _ButtonedEventMixin on _BasicEventContext {
29933048
required double? deltaY,
29943049
double? wheelDeltaX,
29953050
double? wheelDeltaY,
3051+
int? timeStamp,
29963052
}) {
29973053
final Function jsWheelEvent = js_util.getProperty<Function>(domWindow, 'WheelEvent');
29983054
final List<dynamic> eventArgs = <dynamic>[
@@ -3005,6 +3061,7 @@ mixin _ButtonedEventMixin on _BasicEventContext {
30053061
'deltaY': deltaY,
30063062
'wheelDeltaX': wheelDeltaX,
30073063
'wheelDeltaY': wheelDeltaY,
3064+
'timeStamp': timeStamp,
30083065
}
30093066
];
30103067
return js_util.callConstructor<DomEvent>(

0 commit comments

Comments
 (0)