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

Commit d41a890

Browse files
committed
Fix inertia cancel event on macOS Ventura
1 parent 75bfcd7 commit d41a890

File tree

2 files changed

+39
-18
lines changed

2 files changed

+39
-18
lines changed

shell/platform/darwin/macos/framework/Source/FlutterViewController.mm

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@
9393
bool rotate_gesture_active = false;
9494

9595
/**
96-
* System scroll inertia is currently sending us events.
96+
* Time of last scroll momentum event.
9797
*/
98-
bool system_scroll_inertia_active = false;
98+
NSTimeInterval last_scroll_momentum_changed_time = 0;
9999

100100
/**
101101
* Resets all gesture state to default values.
@@ -520,11 +520,11 @@ - (void)dispatchGestureEvent:(nonnull NSEvent*)event {
520520
} else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
521521
[self dispatchMouseEvent:event phase:kHover];
522522
} else {
523-
if (event.momentumPhase == NSEventPhaseBegan) {
524-
_mouseState.system_scroll_inertia_active = true;
525-
} else if (event.momentumPhase == NSEventPhaseEnded ||
526-
event.momentumPhase == NSEventPhaseCancelled) {
527-
_mouseState.system_scroll_inertia_active = false;
523+
// Waiting until the first momentum change event is a workaround for an issue where
524+
// touchesBegan: is called unexpectedly while in low power mode within the interval between
525+
// momentum start and the first momentum change.
526+
if (event.momentumPhase == NSEventPhaseChanged) {
527+
_mouseState.last_scroll_momentum_changed_time = event.timestamp;
528528
}
529529
// Skip momentum update events, the framework will generate scroll momentum.
530530
NSAssert(event.momentumPhase != NSEventPhaseNone,
@@ -548,6 +548,8 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
548548
_mouseState.rotate_gesture_active;
549549
if (event.type == NSEventTypeScrollWheel) {
550550
_mouseState.pan_gesture_active = true;
551+
// Ensure scroll inertia cancel event is not sent afterwards.
552+
_mouseState.last_scroll_momentum_changed_time = 0;
551553
} else if (event.type == NSEventTypeMagnify) {
552554
_mouseState.scale_gesture_active = true;
553555
} else if (event.type == NSEventTypeRotate) {
@@ -840,8 +842,8 @@ - (void)swipeWithEvent:(NSEvent*)event {
840842
- (void)touchesBeganWithEvent:(NSEvent*)event {
841843
NSTouch* touch = event.allTouches.anyObject;
842844
if (touch != nil) {
843-
if (_mouseState.system_scroll_inertia_active) {
844-
// The trackpad has been touched and a scroll gesture is still sending inertia events.
845+
if ((event.timestamp - _mouseState.last_scroll_momentum_changed_time) < 0.010) {
846+
// The trackpad has been touched within 10 ms following a scroll momentum event.
845847
// A scroll inertia cancel message should be sent to the framework.
846848
NSPoint locationInView = [self.flutterView convertPoint:event.locationInWindow fromView:nil];
847849
NSPoint locationInBackingCoordinates =
@@ -857,6 +859,8 @@ - (void)touchesBeganWithEvent:(NSEvent*)event {
857859
};
858860

859861
[_engine sendPointerEvent:flutterEvent];
862+
// Ensure no further scroll inertia cancel event will be sent.
863+
_mouseState.last_scroll_momentum_changed_time = 0;
860864
}
861865
}
862866
}

shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -541,17 +541,39 @@ - (bool)testTrackpadGesturesAreSentToFramework {
541541
[viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
542542
EXPECT_FALSE(called);
543543

544+
// Advance system momentum.
545+
CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
546+
CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
547+
CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
548+
kCGMomentumScrollPhaseContinue);
549+
550+
called = false;
551+
[viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
552+
EXPECT_FALSE(called);
553+
544554
// Mock a touch on the trackpad.
545555
id touchMock = OCMClassMock([NSTouch class]);
546556
NSSet* touchSet = [NSSet setWithObject:touchMock];
547-
id touchEventMock = OCMClassMock([NSEvent class]);
548-
OCMStub([touchEventMock allTouches]).andReturn(touchSet);
557+
id touchEventMock1 = OCMClassMock([NSEvent class]);
558+
OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
549559
CGPoint touchLocation = {0, 0};
550-
OCMStub([touchEventMock locationInWindow]).andReturn(touchLocation);
560+
OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
561+
OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150); // 150 milliseconds.
562+
563+
// Scroll inertia cancel event should not be issued (timestamp too far in the future).
564+
called = false;
565+
[viewController touchesBeganWithEvent:touchEventMock1];
566+
EXPECT_FALSE(called);
567+
568+
// Mock another touch on the trackpad.
569+
id touchEventMock2 = OCMClassMock([NSEvent class]);
570+
OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
571+
OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
572+
OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005); // 5 milliseconds.
551573

552574
// Scroll inertia cancel event should be issued.
553575
called = false;
554-
[viewController touchesBeganWithEvent:touchEventMock];
576+
[viewController touchesBeganWithEvent:touchEventMock2];
555577
EXPECT_TRUE(called);
556578
EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
557579
EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
@@ -566,11 +588,6 @@ - (bool)testTrackpadGesturesAreSentToFramework {
566588
[viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
567589
EXPECT_FALSE(called);
568590

569-
// Scroll inertia cancel event should not be issued after momentum has ended.
570-
called = false;
571-
[viewController touchesBeganWithEvent:touchEventMock];
572-
EXPECT_FALSE(called);
573-
574591
// May-begin and cancel are used while macOS determines which type of gesture to choose.
575592
CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
576593
CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,

0 commit comments

Comments
 (0)