-
Notifications
You must be signed in to change notification settings - Fork 6k
Add support for mice on iOS 13.4+ #19965
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,28 @@ | |
| NSNotificationName const FlutterViewControllerShowHomeIndicator = | ||
| @"FlutterViewControllerShowHomeIndicator"; | ||
|
|
||
| // Struct holding the mouse state. The engine doesn't keep track of which | ||
| // mouse buttons have been pressed, so it's the embedding's responsibility. | ||
| typedef struct MouseState { | ||
| // True if the last event sent to Flutter had at least one mouse button. | ||
| // pressed. | ||
| bool flutter_state_is_down = false; | ||
|
|
||
| // True if kAdd has been sent to Flutter. Used to determine whether | ||
| // to send a kAdd event before sending an incoming mouse event, since | ||
| // Flutter expects pointers to be added before events are sent for them. | ||
| bool flutter_state_is_added = false; | ||
|
|
||
| // Current coordinate of the mouse cursor | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the units logical or real pixels? |
||
| CGPoint location = CGPointZero; | ||
|
|
||
| // Last reported translation for an in-flight pan gesture | ||
| CGPoint last_translation = CGPointZero; | ||
|
|
||
| // The currently pressed buttons, as represented in FlutterPointerEvent. | ||
| uint64_t buttons = 0; | ||
| } MouseState; | ||
|
|
||
| // This is left a FlutterBinaryMessenger privately for now to give people a chance to notice the | ||
| // change. Unfortunately unless you have Werror turned on, incompatible pointers as arguments are | ||
| // just a warning. | ||
|
|
@@ -77,6 +99,11 @@ @implementation FlutterViewController { | |
| // UIScrollView with height zero and a content offset so we can get those events. See also: | ||
| // https://github.com/flutter/flutter/issues/35050 | ||
| fml::scoped_nsobject<UIScrollView> _scrollView; | ||
| #ifdef __IPHONE_13_4 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These don't have to be ifdef'd. I think it's better to limit the amount of conditional compilation. |
||
| fml::scoped_nsobject<UIPointerInteraction> _pointerInteraction API_AVAILABLE(ios(13.4)); | ||
| fml::scoped_nsobject<UIPanGestureRecognizer> _panGestureRecognizer API_AVAILABLE(ios(13.4)); | ||
| MouseState _mouseState; | ||
| #endif | ||
| } | ||
|
|
||
| @synthesize displayingFlutterUI = _displayingFlutterUI; | ||
|
|
@@ -528,6 +555,19 @@ - (void)viewDidLoad { | |
| << "FlutterViewController's view is loaded but is not attached to a FlutterEngine"; | ||
| [_engine.get() attachView]; | ||
|
|
||
| #ifdef __IPHONE_13_4 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also remove this preprocessor guard. |
||
| if (@available(iOS 13.4, *)) { | ||
| _pointerInteraction.reset([[UIPointerInteraction alloc] initWithDelegate:self]); | ||
| [self.view addInteraction:_pointerInteraction]; | ||
|
|
||
| _panGestureRecognizer.reset( | ||
| [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(scrollEvent:)]); | ||
| _panGestureRecognizer.get().allowedScrollTypesMask = UIScrollTypeMaskAll; | ||
| _panGestureRecognizer.get().allowedTouchTypes = @[ @(UITouchTypeIndirectPointer) ]; | ||
| [_flutterView.get() addGestureRecognizer:_panGestureRecognizer.get()]; | ||
| } | ||
| #endif | ||
|
|
||
| [super viewDidLoad]; | ||
| } | ||
|
|
||
|
|
@@ -685,8 +725,9 @@ - (void)goToApplicationLifecycle:(nonnull NSString*)state { | |
| return flutter::PointerData::DeviceKind::kTouch; | ||
| case UITouchTypeStylus: | ||
| return flutter::PointerData::DeviceKind::kStylus; | ||
| case UITouchTypeIndirectPointer: | ||
| return flutter::PointerData::DeviceKind::kMouse; | ||
| default: | ||
| // TODO(53696): Handle the UITouchTypeIndirectPointer enum value. | ||
| FML_DLOG(INFO) << "Unhandled touch type: " << touch.type; | ||
| break; | ||
| } | ||
|
|
@@ -1286,4 +1327,67 @@ - (BOOL)isPresentingViewController { | |
| return self.presentedViewController != nil || self.isPresentingViewControllerAnimating; | ||
| } | ||
|
|
||
| #ifdef __IPHONE_13_4 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here too |
||
| - (flutter::PointerData)generatePointerDataForMouse { | ||
| const CGFloat scale = [UIScreen mainScreen].scale; | ||
|
|
||
| flutter::PointerData pointer_data; | ||
|
|
||
| pointer_data.Clear(); | ||
|
|
||
| pointer_data.kind = flutter::PointerData::DeviceKind::kMouse; | ||
| pointer_data.change = _mouseState.flutter_state_is_added ? flutter::PointerData::Change::kAdd | ||
| : flutter::PointerData::Change::kHover; | ||
| pointer_data.pointer_identifier = reinterpret_cast<int64_t>(_pointerInteraction.get()); | ||
|
|
||
| pointer_data.physical_x = _mouseState.location.x * scale; | ||
| pointer_data.physical_y = _mouseState.location.y * scale; | ||
|
|
||
| return pointer_data; | ||
| } | ||
|
|
||
| - (UIPointerRegion*)pointerInteraction:(UIPointerInteraction*)interaction | ||
| regionForRequest:(UIPointerRegionRequest*)request | ||
| defaultRegion:(UIPointerRegion*)defaultRegion API_AVAILABLE(ios(13.4)) { | ||
| if (request != nil) { | ||
| auto packet = std::make_unique<flutter::PointerDataPacket>(1); | ||
| _mouseState.location = request.location; | ||
|
|
||
| flutter::PointerData pointer_data = [self generatePointerDataForMouse]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: A static C function for |
||
|
|
||
| pointer_data.signal_kind = flutter::PointerData::SignalKind::kNone; | ||
| packet->SetPointerData(0, pointer_data); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please annotate what "0" means by pulling it into a variable or with arg comments. |
||
|
|
||
| [_engine.get() dispatchPointerDataPacket:std::move(packet)]; | ||
| } | ||
| return nil; | ||
| } | ||
|
|
||
| - (void)scrollEvent:(UIPanGestureRecognizer*)recognizer { | ||
| CGPoint translation = [recognizer translationInView:self.view]; | ||
| const CGFloat scale = [UIScreen mainScreen].scale; | ||
|
|
||
| auto packet = std::make_unique<flutter::PointerDataPacket>(1); | ||
|
|
||
| flutter::PointerData pointer_data = [self generatePointerDataForMouse]; | ||
| pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll; | ||
| pointer_data.scroll_delta_x = (translation.x - _mouseState.last_translation.x) * scale; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super nit: you are scaling these values twice, once when you receive them and once when they are used as "last_translation". You could avoid the multiplication by storing the scaled value. |
||
| pointer_data.scroll_delta_y = -(translation.y - _mouseState.last_translation.y) * scale; | ||
|
|
||
| // The translation reported by UIPanGestureRecognizer is the total translation | ||
| // generated by the pan gesture since the gesture began. We need to be able | ||
| // to keep track of the last translation value in order to generate the deltaX | ||
| // and deltaY coordinates for each subsequent scroll event. | ||
| if (recognizer.state != UIGestureRecognizerStateEnded) { | ||
| _mouseState.last_translation = translation; | ||
| } else { | ||
| _mouseState.last_translation = CGPointZero; | ||
| } | ||
|
|
||
| packet->SetPointerData(0, pointer_data); | ||
|
|
||
| [_engine.get() dispatchPointerDataPacket:std::move(packet)]; | ||
| } | ||
| #endif | ||
|
|
||
| @end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need this, the presence of
UIPointerInteractionDelegatewill exist depending on the SDK used for compilation. We don't need to support old versions of Xcode.