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
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,25 @@
*/
bool has_pending_exit = false;

/*
* Whether or not a kPanZoomStart has been sent since the last kAdd/kPanZoomEnd.
*/
bool flutter_state_is_pan_zoom_started = false;

/**
* Pan gesture is currently sending us events.
* State of pan gesture.
*/
bool pan_gesture_active = false;
NSEventPhase pan_gesture_phase = NSEventPhaseNone;

/**
* Scale gesture is currently sending us events.
* State of scale gesture.
*/
bool scale_gesture_active = false;
NSEventPhase scale_gesture_phase = NSEventPhaseNone;

/**
* Rotate gesture is currently sending use events.
* State of rotate gesture.
*/
bool rotate_gesture_active = false;
NSEventPhase rotate_gesture_phase = NSEventPhaseNone;

/**
* Time of last scroll momentum event.
Expand All @@ -108,6 +113,10 @@ void GestureReset() {
delta_y = 0;
scale = 0;
rotation = 0;
flutter_state_is_pan_zoom_started = false;
pan_gesture_phase = NSEventPhaseNone;
scale_gesture_phase = NSEventPhaseNone;
rotate_gesture_phase = NSEventPhaseNone;
}

/**
Expand Down Expand Up @@ -679,32 +688,42 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {

// Multiple gesture recognizers could be active at once, we can't send multiple kPanZoomStart.
// For example: rotation and magnification.
if (phase == kPanZoomStart || phase == kPanZoomEnd) {
if (event.type == NSEventTypeScrollWheel) {
_mouseState.pan_gesture_phase = event.phase;
} else if (event.type == NSEventTypeMagnify) {
_mouseState.scale_gesture_phase = event.phase;
} else if (event.type == NSEventTypeRotate) {
_mouseState.rotate_gesture_phase = event.phase;
}
}
if (phase == kPanZoomStart) {
bool gestureAlreadyDown = _mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||
_mouseState.rotate_gesture_active;
if (event.type == NSEventTypeScrollWheel) {
_mouseState.pan_gesture_active = true;
// Ensure scroll inertia cancel event is not sent afterwards.
_mouseState.last_scroll_momentum_changed_time = 0;
} else if (event.type == NSEventTypeMagnify) {
_mouseState.scale_gesture_active = true;
} else if (event.type == NSEventTypeRotate) {
_mouseState.rotate_gesture_active = true;
}
if (gestureAlreadyDown) {
if (_mouseState.flutter_state_is_pan_zoom_started) {
// Already started on a previous gesture type
return;
}
_mouseState.flutter_state_is_pan_zoom_started = true;
}
if (phase == kPanZoomEnd) {
if (event.type == NSEventTypeScrollWheel) {
_mouseState.pan_gesture_active = false;
} else if (event.type == NSEventTypeMagnify) {
_mouseState.scale_gesture_active = false;
} else if (event.type == NSEventTypeRotate) {
_mouseState.rotate_gesture_active = false;
if (!_mouseState.flutter_state_is_pan_zoom_started) {
// NSEventPhaseCancelled is sometimes received at incorrect times in the state
// machine, just ignore it here if it doesn't make sense
// (we have no active gesture to cancel).
NSAssert(event.phase == NSEventPhaseCancelled,
@"Received gesture event with unexpected phase");
return;
}
if (_mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||
_mouseState.rotate_gesture_active) {
// NSEventPhase values are powers of two, we can use this to inspect merged phases.
NSEventPhase all_gestures_fields = _mouseState.pan_gesture_phase |
_mouseState.scale_gesture_phase |
_mouseState.rotate_gesture_phase;
NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;
if ((all_gestures_fields & active_mask) != 0) {
// Even though this gesture type ended, a different type is still active.
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,12 @@ - (bool)testTrackpadGesturesAreSentToFramework:(id)engineMock {
EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);

// Test that stray NSEventPhaseCancelled event does not crash
called = false;
[viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
NSEventPhaseCancelled, 0, 0)];
EXPECT_FALSE(called);

return true;
}

Expand Down