8 #include <Carbon/Carbon.h>
9 #import <objc/message.h>
20 #import "flutter/shell/platform/embedder/embedder.h"
28 static constexpr int32_t kMousePointerDeviceId = 0;
29 static constexpr int32_t kPointerPanZoomDeviceId = 1;
34 static constexpr
double kTrackpadTouchInertiaCancelWindowMs = 0.050;
67 bool flutter_state_is_added =
false;
72 bool flutter_state_is_down =
false;
81 bool has_pending_exit =
false;
86 bool pan_gesture_active =
false;
91 bool scale_gesture_active =
false;
96 bool rotate_gesture_active =
false;
101 NSTimeInterval last_scroll_momentum_changed_time = 0;
106 void GestureReset() {
117 flutter_state_is_added =
false;
118 flutter_state_is_down =
false;
119 has_pending_exit =
false;
132 NSData* currentKeyboardLayoutData() {
133 TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();
134 CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
135 if (layout_data == nil) {
140 source = TISCopyCurrentKeyboardLayoutInputSource();
141 layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
143 return (__bridge_transfer NSData*)CFRetain(layout_data);
148 #pragma mark - Private interface declaration.
165 - (void)setBackgroundColor:(NSColor*)color;
177 @property(nonatomic) NSTrackingArea* trackingArea;
182 @property(nonatomic) MouseState mouseState;
187 @property(nonatomic)
id keyUpMonitor;
198 @property(nonatomic) NSData* keyboardLayoutData;
203 - (BOOL)launchEngine;
209 - (void)configureTrackingArea;
214 - (void)initializeKeyboard;
221 - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
226 - (void)dispatchGestureEvent:(nonnull NSEvent*)event;
231 - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
239 - (void)onKeyboardLayoutChanged;
243 #pragma mark - Private dependant functions
246 void OnKeyboardLayoutChanged(CFNotificationCenterRef center,
250 CFDictionaryRef userInfo) {
252 if (controller != nil) {
253 [controller onKeyboardLayoutChanged];
258 #pragma mark - FlutterViewWrapper implementation.
265 - (instancetype)initWithFlutterView:(
FlutterView*)view
267 self = [
super initWithFrame:NSZeroRect];
271 view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
272 [
self addSubview:view];
277 - (void)setBackgroundColor:(NSColor*)color {
278 [_flutterView setBackgroundColor:color];
281 - (BOOL)performKeyEquivalent:(NSEvent*)event {
288 if (
self.window.firstResponder != _flutterView || [
_controller isDispatchingKeyEvent:event]) {
289 return [
super performKeyEquivalent:event];
291 [_flutterView keyDown:event];
295 - (NSArray*)accessibilityChildren {
296 return @[ _flutterView ];
299 - (void)mouseDown:(NSEvent*)event {
310 [
self.nextResponder mouseDown:event];
313 - (void)mouseUp:(NSEvent*)event {
324 [
self.nextResponder mouseUp:event];
329 #pragma mark - FlutterViewController implementation.
335 std::shared_ptr<flutter::AccessibilityBridgeMac>
_bridge;
344 @synthesize viewId = _viewId;
345 @dynamic accessibilityBridge;
353 project:controller->_project
354 allowHeadlessExecution:NO];
356 NSCAssert(controller.
engine == nil,
357 @"The FlutterViewController is unexpectedly attached to "
358 @"engine %@ before initialization.",
360 [engine addViewController:controller];
361 NSCAssert(controller.
engine != nil,
362 @"The FlutterViewController unexpectedly stays unattached after initialization. "
363 @"In unit tests, this is likely because either the FlutterViewController or "
364 @"the FlutterEngine is mocked. Please subclass these classes instead.",
365 controller.
engine, controller.viewId);
366 controller->_mouseTrackingMode = kFlutterMouseTrackingModeInKeyWindow;
368 [controller initializeKeyboard];
369 [controller notifySemanticsEnabledChanged];
371 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
373 CFNotificationCenterAddObserver(cfCenter, (__bridge
void*)weakSelf, OnKeyboardLayoutChanged,
374 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
375 CFNotificationSuspensionBehaviorDeliverImmediately);
378 - (instancetype)initWithCoder:(NSCoder*)coder {
379 self = [
super initWithCoder:coder];
380 NSAssert(
self,
@"Super init cannot be nil");
382 CommonInit(
self, nil);
386 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
387 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
388 NSAssert(
self,
@"Super init cannot be nil");
390 CommonInit(
self, nil);
395 self = [
super initWithNibName:nil bundle:nil];
396 NSAssert(
self,
@"Super init cannot be nil");
399 CommonInit(
self, nil);
404 nibName:(nullable NSString*)nibName
405 bundle:(nullable NSBundle*)nibBundle {
406 NSAssert(engine != nil,
@"Engine is required");
408 self = [
super initWithNibName:nibName bundle:nibBundle];
410 CommonInit(
self, engine);
416 - (BOOL)isDispatchingKeyEvent:(NSEvent*)event {
417 return [_keyboardManager isDispatchingKeyEvent:event];
422 id<MTLDevice> device = _engine.renderer.device;
423 id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;
424 if (!device || !commandQueue) {
425 NSLog(
@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
428 flutterView = [
self createFlutterViewWithMTLDevice:device commandQueue:commandQueue];
429 if (_backgroundColor != nil) {
434 self.view = wrapperView;
435 _flutterView = flutterView;
438 - (void)viewDidLoad {
439 [
self configureTrackingArea];
440 [
self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];
441 [
self.view setWantsRestingTouches:YES];
444 - (void)viewWillAppear {
445 [
super viewWillAppear];
446 if (!_engine.running) {
449 [
self listenForMetaModifiedKeyUpEvents];
452 - (void)viewWillDisappear {
455 [NSEvent removeMonitor:_keyUpMonitor];
460 if ([
self attached]) {
461 [_engine removeViewController:self];
463 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
464 CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge
void*)
self);
467 #pragma mark - Public methods
469 - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
470 if (_mouseTrackingMode == mode) {
473 _mouseTrackingMode = mode;
474 [
self configureTrackingArea];
477 - (void)setBackgroundColor:(NSColor*)color {
478 _backgroundColor = color;
479 [_flutterView setBackgroundColor:_backgroundColor];
483 NSAssert([
self attached],
@"This view controller is not attached.");
487 - (void)onPreEngineRestart {
488 [
self initializeKeyboard];
491 - (void)notifySemanticsEnabledChanged {
492 BOOL mySemanticsEnabled = !!
_bridge;
493 BOOL newSemanticsEnabled = _engine.semanticsEnabled;
494 if (newSemanticsEnabled == mySemanticsEnabled) {
497 if (newSemanticsEnabled) {
498 _bridge = [
self createAccessibilityBridgeWithEngine:_engine];
501 _flutterView.accessibilityChildren = nil;
504 NSAssert(newSemanticsEnabled == !!
_bridge,
@"Failed to update semantics for the view.");
507 - (std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
514 NSAssert(_engine == nil,
@"Already attached to an engine %@.", _engine);
518 [_threadSynchronizer registerView:_viewId];
521 - (void)detachFromEngine {
522 NSAssert(_engine != nil,
@"Not attached to any engine.");
523 [_threadSynchronizer deregisterView:_viewId];
529 return _engine != nil;
532 - (void)updateSemantics:(const FlutterSemanticsUpdate2*)update {
533 NSAssert(_engine.semanticsEnabled,
@"Semantics must be enabled.");
534 if (!_engine.semanticsEnabled) {
537 for (
size_t i = 0; i < update->node_count; i++) {
538 const FlutterSemanticsNode2* node = update->nodes[i];
539 _bridge->AddFlutterSemanticsNodeUpdate(*node);
542 for (
size_t i = 0; i < update->custom_action_count; i++) {
543 const FlutterSemanticsCustomAction2* action = update->custom_actions[i];
544 _bridge->AddFlutterSemanticsCustomActionUpdate(*action);
550 if (!
self.viewLoaded) {
554 auto root =
_bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
556 if ([
self.flutterView.accessibilityChildren count] == 0) {
557 NSAccessibilityElement* native_root = root->GetNativeViewAccessible();
558 self.flutterView.accessibilityChildren = @[ native_root ];
561 self.flutterView.accessibilityChildren = nil;
565 #pragma mark - Private methods
567 - (BOOL)launchEngine {
568 if (![_engine runWithEntrypoint:nil]) {
578 - (void)listenForMetaModifiedKeyUpEvents {
579 if (_keyUpMonitor != nil) {
585 _keyUpMonitor = [NSEvent
586 addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
587 handler:^NSEvent*(NSEvent* event) {
590 NSResponder* firstResponder = [[event window] firstResponder];
591 if (weakSelf.viewLoaded && weakSelf.flutterView &&
592 (firstResponder == weakSelf.flutterView ||
593 firstResponder == weakSelf.textInputPlugin) &&
594 ([event modifierFlags] & NSEventModifierFlagCommand) &&
595 ([event type] == NSEventTypeKeyUp)) {
596 [weakSelf keyUp:event];
602 - (void)configureTrackingArea {
603 if (!
self.viewLoaded) {
608 if (_mouseTrackingMode != kFlutterMouseTrackingModeNone &&
self.flutterView) {
609 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
610 NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
611 switch (_mouseTrackingMode) {
612 case kFlutterMouseTrackingModeInKeyWindow:
613 options |= NSTrackingActiveInKeyWindow;
615 case kFlutterMouseTrackingModeInActiveApp:
616 options |= NSTrackingActiveInActiveApp;
618 case kFlutterMouseTrackingModeAlways:
619 options |= NSTrackingActiveAlways;
622 NSLog(
@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
625 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
629 [
self.flutterView addTrackingArea:_trackingArea];
630 }
else if (_trackingArea) {
631 [
self.flutterView removeTrackingArea:_trackingArea];
636 - (void)initializeKeyboard {
642 - (void)dispatchMouseEvent:(nonnull NSEvent*)event {
643 FlutterPointerPhase phase = _mouseState.buttons == 0
644 ? (_mouseState.flutter_state_is_down ? kUp : kHover)
645 : (_mouseState.flutter_state_is_down ? kMove : kDown);
646 [
self dispatchMouseEvent:event phase:phase];
649 - (void)dispatchGestureEvent:(nonnull NSEvent*)event {
650 if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) {
651 [
self dispatchMouseEvent:event phase:kPanZoomStart];
652 }
else if (event.phase == NSEventPhaseChanged) {
653 [
self dispatchMouseEvent:event phase:kPanZoomUpdate];
654 }
else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {
655 [
self dispatchMouseEvent:event phase:kPanZoomEnd];
656 }
else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
657 [
self dispatchMouseEvent:event phase:kHover];
662 if (event.momentumPhase == NSEventPhaseChanged) {
663 _mouseState.last_scroll_momentum_changed_time =
event.timestamp;
666 NSAssert(event.momentumPhase != NSEventPhaseNone,
667 @"Received gesture event with unexpected phase");
671 - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
672 NSAssert(
self.viewLoaded,
@"View must be loaded before it handles the mouse event");
676 if (_mouseState.flutter_state_is_added && phase == kAdd) {
682 if (phase == kPanZoomStart) {
683 bool gestureAlreadyDown = _mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||
684 _mouseState.rotate_gesture_active;
685 if (event.type == NSEventTypeScrollWheel) {
686 _mouseState.pan_gesture_active =
true;
688 _mouseState.last_scroll_momentum_changed_time = 0;
689 }
else if (event.type == NSEventTypeMagnify) {
690 _mouseState.scale_gesture_active =
true;
691 }
else if (event.type == NSEventTypeRotate) {
692 _mouseState.rotate_gesture_active =
true;
694 if (gestureAlreadyDown) {
698 if (phase == kPanZoomEnd) {
699 if (event.type == NSEventTypeScrollWheel) {
700 _mouseState.pan_gesture_active =
false;
701 }
else if (event.type == NSEventTypeMagnify) {
702 _mouseState.scale_gesture_active =
false;
703 }
else if (event.type == NSEventTypeRotate) {
704 _mouseState.rotate_gesture_active =
false;
706 if (_mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||
707 _mouseState.rotate_gesture_active) {
714 if (!_mouseState.flutter_state_is_added && phase != kAdd) {
716 NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
717 location:event.locationInWindow
719 timestamp:event.timestamp
720 windowNumber:event.windowNumber
725 [
self dispatchMouseEvent:addEvent phase:kAdd];
728 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
729 NSPoint locationInBackingCoordinates = [
self.flutterView convertPointToBacking:locationInView];
730 int32_t device = kMousePointerDeviceId;
731 FlutterPointerDeviceKind deviceKind = kFlutterPointerDeviceKindMouse;
732 if (phase == kPanZoomStart || phase == kPanZoomUpdate || phase == kPanZoomEnd) {
733 device = kPointerPanZoomDeviceId;
734 deviceKind = kFlutterPointerDeviceKindTrackpad;
736 FlutterPointerEvent flutterEvent = {
737 .struct_size =
sizeof(flutterEvent),
739 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
740 .x = locationInBackingCoordinates.x,
741 .y = -locationInBackingCoordinates.y,
743 .device_kind = deviceKind,
745 .buttons = phase == kAdd ? 0 : _mouseState.buttons,
748 if (phase == kPanZoomUpdate) {
749 if (event.type == NSEventTypeScrollWheel) {
750 _mouseState.delta_x += event.scrollingDeltaX * self.flutterView.layer.contentsScale;
751 _mouseState.delta_y += event.scrollingDeltaY * self.flutterView.layer.contentsScale;
752 }
else if (event.type == NSEventTypeMagnify) {
753 _mouseState.scale += event.magnification;
754 }
else if (event.type == NSEventTypeRotate) {
755 _mouseState.rotation += event.rotation * (-M_PI / 180.0);
757 flutterEvent.pan_x = _mouseState.delta_x;
758 flutterEvent.pan_y = _mouseState.delta_y;
760 flutterEvent.scale = pow(2.0, _mouseState.scale);
761 flutterEvent.rotation = _mouseState.rotation;
762 }
else if (phase == kPanZoomEnd) {
763 _mouseState.GestureReset();
764 }
else if (phase != kPanZoomStart && event.type == NSEventTypeScrollWheel) {
765 flutterEvent.signal_kind = kFlutterPointerSignalKindScroll;
767 double pixelsPerLine = 1.0;
768 if (!event.hasPreciseScrollingDeltas) {
774 pixelsPerLine = 40.0;
776 double scaleFactor =
self.flutterView.layer.contentsScale;
785 double scaledDeltaX = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
786 double scaledDeltaY = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
787 if (event.modifierFlags & NSShiftKeyMask) {
788 flutterEvent.scroll_delta_x = scaledDeltaY;
789 flutterEvent.scroll_delta_y = scaledDeltaX;
791 flutterEvent.scroll_delta_x = scaledDeltaX;
792 flutterEvent.scroll_delta_y = scaledDeltaY;
796 [_keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
797 [_engine sendPointerEvent:flutterEvent];
800 if (phase == kDown) {
801 _mouseState.flutter_state_is_down =
true;
802 }
else if (phase == kUp) {
803 _mouseState.flutter_state_is_down =
false;
804 if (_mouseState.has_pending_exit) {
805 [
self dispatchMouseEvent:event phase:kRemove];
806 _mouseState.has_pending_exit =
false;
808 }
else if (phase == kAdd) {
809 _mouseState.flutter_state_is_added =
true;
810 }
else if (phase == kRemove) {
815 - (void)onAccessibilityStatusChanged:(BOOL)enabled {
816 if (!enabled &&
self.viewLoaded && [_textInputPlugin isFirstResponder]) {
821 [
self.view addSubview:_textInputPlugin];
825 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
827 return std::make_shared<flutter::AccessibilityBridgeMac>(engine,
self);
830 - (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(
id<MTLDevice>)device
831 commandQueue:(
id<MTLCommandQueue>)commandQueue {
832 return [[
FlutterView alloc] initWithMTLDevice:device
833 commandQueue:commandQueue
835 threadSynchronizer:_threadSynchronizer
839 - (void)onKeyboardLayoutChanged {
840 _keyboardLayoutData = nil;
846 - (NSString*)lookupKeyForAsset:(NSString*)asset {
850 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
854 #pragma mark - FlutterViewReshapeListener
859 - (void)viewDidReshape:(NSView*)view {
860 [_engine updateWindowMetricsForViewController:self];
863 #pragma mark - FlutterPluginRegistry
866 return [_engine registrarForPlugin:pluginName];
869 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
870 return [_engine valuePublishedByPlugin:pluginKey];
873 #pragma mark - FlutterKeyboardViewDelegate
875 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
876 callback:(nullable FlutterKeyEventCallback)callback
877 userData:(nullable
void*)userData {
878 [_engine sendKeyEvent:event callback:callback userData:userData];
882 return _engine.binaryMessenger;
885 - (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {
886 return [_textInputPlugin handleKeyEvent:event];
893 - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
894 if (_keyboardLayoutData == nil) {
895 _keyboardLayoutData = currentKeyboardLayoutData();
897 const UCKeyboardLayout* layout =
reinterpret_cast<const UCKeyboardLayout*
>(
898 CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));
900 UInt32 deadKeyState = 0;
901 UniCharCount stringLength = 0;
904 UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;
905 UInt32 keyboardType = LMGetKbdLast();
907 bool isDeadKey =
false;
909 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
910 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
912 if (status == noErr && stringLength == 0 && deadKeyState != 0) {
915 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
916 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
919 if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {
920 return LayoutClue{resultChar, isDeadKey};
922 return LayoutClue{0,
false};
925 - (nonnull NSDictionary*)getPressedState {
926 return [_keyboardManager getPressedState];
929 #pragma mark - NSResponder
931 - (BOOL)acceptsFirstResponder {
935 - (void)keyDown:(NSEvent*)event {
936 [_keyboardManager handleEvent:event];
939 - (void)keyUp:(NSEvent*)event {
940 [_keyboardManager handleEvent:event];
943 - (void)flagsChanged:(NSEvent*)event {
944 [_keyboardManager handleEvent:event];
947 - (void)mouseEntered:(NSEvent*)event {
948 if (_mouseState.has_pending_exit) {
949 _mouseState.has_pending_exit =
false;
951 [
self dispatchMouseEvent:event phase:kAdd];
955 - (void)mouseExited:(NSEvent*)event {
956 if (_mouseState.buttons != 0) {
957 _mouseState.has_pending_exit =
true;
960 [
self dispatchMouseEvent:event phase:kRemove];
963 - (void)mouseDown:(NSEvent*)event {
964 _mouseState.buttons |= kFlutterPointerButtonMousePrimary;
965 [
self dispatchMouseEvent:event];
968 - (void)mouseUp:(NSEvent*)event {
969 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMousePrimary);
970 [
self dispatchMouseEvent:event];
973 - (void)mouseDragged:(NSEvent*)event {
974 [
self dispatchMouseEvent:event];
977 - (void)rightMouseDown:(NSEvent*)event {
978 _mouseState.buttons |= kFlutterPointerButtonMouseSecondary;
979 [
self dispatchMouseEvent:event];
982 - (void)rightMouseUp:(NSEvent*)event {
983 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMouseSecondary);
984 [
self dispatchMouseEvent:event];
987 - (void)rightMouseDragged:(NSEvent*)event {
988 [
self dispatchMouseEvent:event];
991 - (void)otherMouseDown:(NSEvent*)event {
992 _mouseState.buttons |= (1 <<
event.buttonNumber);
993 [
self dispatchMouseEvent:event];
996 - (void)otherMouseUp:(NSEvent*)event {
997 _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
998 [
self dispatchMouseEvent:event];
1001 - (void)otherMouseDragged:(NSEvent*)event {
1002 [
self dispatchMouseEvent:event];
1005 - (void)mouseMoved:(NSEvent*)event {
1006 [
self dispatchMouseEvent:event];
1009 - (void)scrollWheel:(NSEvent*)event {
1010 [
self dispatchGestureEvent:event];
1013 - (void)magnifyWithEvent:(NSEvent*)event {
1014 [
self dispatchGestureEvent:event];
1017 - (void)rotateWithEvent:(NSEvent*)event {
1018 [
self dispatchGestureEvent:event];
1021 - (void)swipeWithEvent:(NSEvent*)event {
1025 - (void)touchesBeganWithEvent:(NSEvent*)event {
1026 NSTouch* touch =
event.allTouches.anyObject;
1028 if ((event.timestamp - _mouseState.last_scroll_momentum_changed_time) <
1029 kTrackpadTouchInertiaCancelWindowMs) {
1032 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
1033 NSPoint locationInBackingCoordinates =
1034 [
self.flutterView convertPointToBacking:locationInView];
1035 FlutterPointerEvent flutterEvent = {
1036 .struct_size =
sizeof(flutterEvent),
1037 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
1038 .x = locationInBackingCoordinates.x,
1039 .y = -locationInBackingCoordinates.y,
1040 .device = kPointerPanZoomDeviceId,
1041 .signal_kind = kFlutterPointerSignalKindScrollInertiaCancel,
1042 .device_kind = kFlutterPointerDeviceKindTrackpad,
1045 [_engine sendPointerEvent:flutterEvent];
1047 _mouseState.last_scroll_momentum_changed_time = 0;