8 #include <Carbon/Carbon.h>
9 #import <objc/message.h>
20 #import "flutter/shell/platform/embedder/embedder.h"
22 #pragma mark - Static types and data.
30 static constexpr int32_t kMousePointerDeviceId = 0;
31 static constexpr int32_t kPointerPanZoomDeviceId = 1;
36 static constexpr
double kTrackpadTouchInertiaCancelWindowMs = 0.050;
69 bool flutter_state_is_added =
false;
74 bool flutter_state_is_down =
false;
83 bool has_pending_exit =
false;
88 bool flutter_state_is_pan_zoom_started =
false;
93 NSEventPhase pan_gesture_phase = NSEventPhaseNone;
98 NSEventPhase scale_gesture_phase = NSEventPhaseNone;
103 NSEventPhase rotate_gesture_phase = NSEventPhaseNone;
108 NSTimeInterval last_scroll_momentum_changed_time = 0;
113 void GestureReset() {
118 flutter_state_is_pan_zoom_started =
false;
119 pan_gesture_phase = NSEventPhaseNone;
120 scale_gesture_phase = NSEventPhaseNone;
121 rotate_gesture_phase = NSEventPhaseNone;
128 flutter_state_is_added =
false;
129 flutter_state_is_down =
false;
130 has_pending_exit =
false;
138 #pragma mark - Private interface declaration.
155 - (void)setBackgroundColor:(NSColor*)color;
167 @property(nonatomic) NSTrackingArea* trackingArea;
172 @property(nonatomic) MouseState mouseState;
177 @property(nonatomic)
id keyUpMonitor;
188 @property(nonatomic) NSData* keyboardLayoutData;
193 - (BOOL)launchEngine;
199 - (void)configureTrackingArea;
204 - (void)initializeKeyboard;
211 - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
216 - (void)dispatchGestureEvent:(nonnull NSEvent*)event;
221 - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
229 - (void)onKeyboardLayoutChanged;
233 #pragma mark - FlutterViewWrapper implementation.
242 CFDictionaryRef userInfo) {
244 if (controller != nil) {
245 [controller onKeyboardLayoutChanged];
254 - (instancetype)initWithFlutterView:(
FlutterView*)view
256 self = [
super initWithFrame:NSZeroRect];
260 view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
261 [
self addSubview:view];
266 - (void)setBackgroundColor:(NSColor*)color {
267 [_flutterView setBackgroundColor:color];
270 - (BOOL)performKeyEquivalent:(NSEvent*)event {
277 if (
self.window.firstResponder != _flutterView || [
_controller isDispatchingKeyEvent:event]) {
278 return [
super performKeyEquivalent:event];
280 [_flutterView keyDown:event];
284 - (NSArray*)accessibilityChildren {
285 return @[ _flutterView ];
288 - (void)mouseDown:(NSEvent*)event {
299 [
self.nextResponder mouseDown:event];
302 - (void)mouseUp:(NSEvent*)event {
313 [
self.nextResponder mouseUp:event];
318 #pragma mark - FlutterViewController implementation.
324 std::shared_ptr<flutter::AccessibilityBridgeMac>
_bridge;
333 @synthesize viewId = _viewId;
334 @dynamic accessibilityBridge;
342 project:controller->_project
343 allowHeadlessExecution:NO];
345 NSCAssert(controller.
engine == nil,
346 @"The FlutterViewController is unexpectedly attached to "
347 @"engine %@ before initialization.",
349 [engine addViewController:controller];
350 NSCAssert(controller.
engine != nil,
351 @"The FlutterViewController unexpectedly stays unattached after initialization. "
352 @"In unit tests, this is likely because either the FlutterViewController or "
353 @"the FlutterEngine is mocked. Please subclass these classes instead.",
354 controller.
engine, controller.viewId);
355 controller->_mouseTrackingMode = kFlutterMouseTrackingModeInKeyWindow;
357 [controller initializeKeyboard];
358 [controller notifySemanticsEnabledChanged];
360 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
363 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
364 CFNotificationSuspensionBehaviorDeliverImmediately);
367 - (instancetype)initWithCoder:(NSCoder*)coder {
368 self = [
super initWithCoder:coder];
369 NSAssert(
self,
@"Super init cannot be nil");
371 CommonInit(
self, nil);
375 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
376 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
377 NSAssert(
self,
@"Super init cannot be nil");
379 CommonInit(
self, nil);
384 self = [
super initWithNibName:nil bundle:nil];
385 NSAssert(
self,
@"Super init cannot be nil");
388 CommonInit(
self, nil);
393 nibName:(nullable NSString*)nibName
394 bundle:(nullable NSBundle*)nibBundle {
395 NSAssert(engine != nil,
@"Engine is required");
397 self = [
super initWithNibName:nibName bundle:nibBundle];
399 CommonInit(
self, engine);
405 - (BOOL)isDispatchingKeyEvent:(NSEvent*)event {
406 return [_keyboardManager isDispatchingKeyEvent:event];
411 id<MTLDevice> device = _engine.renderer.device;
412 id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;
413 if (!device || !commandQueue) {
414 NSLog(
@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
417 flutterView = [
self createFlutterViewWithMTLDevice:device commandQueue:commandQueue];
418 if (_backgroundColor != nil) {
423 self.view = wrapperView;
424 _flutterView = flutterView;
427 - (void)viewDidLoad {
428 [
self configureTrackingArea];
429 [
self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];
430 [
self.view setWantsRestingTouches:YES];
431 [_engine viewControllerViewDidLoad:self];
434 - (void)viewWillAppear {
435 [
super viewWillAppear];
436 if (!_engine.running) {
439 [
self listenForMetaModifiedKeyUpEvents];
442 - (void)viewWillDisappear {
445 [NSEvent removeMonitor:_keyUpMonitor];
450 if ([
self attached]) {
451 [_engine removeViewController:self];
453 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
454 CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge
void*)
self);
457 #pragma mark - Public methods
459 - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
460 if (_mouseTrackingMode == mode) {
463 _mouseTrackingMode = mode;
464 [
self configureTrackingArea];
467 - (void)setBackgroundColor:(NSColor*)color {
468 _backgroundColor = color;
469 [_flutterView setBackgroundColor:_backgroundColor];
473 NSAssert([
self attached],
@"This view controller is not attached.");
477 - (void)onPreEngineRestart {
478 [
self initializeKeyboard];
481 - (void)notifySemanticsEnabledChanged {
482 BOOL mySemanticsEnabled = !!
_bridge;
483 BOOL newSemanticsEnabled = _engine.semanticsEnabled;
484 if (newSemanticsEnabled == mySemanticsEnabled) {
487 if (newSemanticsEnabled) {
488 _bridge = [
self createAccessibilityBridgeWithEngine:_engine];
491 _flutterView.accessibilityChildren = nil;
494 NSAssert(newSemanticsEnabled == !!
_bridge,
@"Failed to update semantics for the view.");
497 - (std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
504 NSAssert(_engine == nil,
@"Already attached to an engine %@.", _engine);
508 [_threadSynchronizer registerView:_viewId];
511 - (void)detachFromEngine {
512 NSAssert(_engine != nil,
@"Not attached to any engine.");
513 [_threadSynchronizer deregisterView:_viewId];
519 return _engine != nil;
522 - (void)updateSemantics:(const FlutterSemanticsUpdate2*)update {
523 NSAssert(_engine.semanticsEnabled,
@"Semantics must be enabled.");
524 if (!_engine.semanticsEnabled) {
527 for (
size_t i = 0; i < update->node_count; i++) {
528 const FlutterSemanticsNode2* node = update->nodes[i];
529 _bridge->AddFlutterSemanticsNodeUpdate(*node);
532 for (
size_t i = 0; i < update->custom_action_count; i++) {
533 const FlutterSemanticsCustomAction2* action = update->custom_actions[i];
534 _bridge->AddFlutterSemanticsCustomActionUpdate(*action);
540 if (!
self.viewLoaded) {
544 auto root =
_bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
546 if ([
self.flutterView.accessibilityChildren count] == 0) {
547 NSAccessibilityElement* native_root = root->GetNativeViewAccessible();
548 self.flutterView.accessibilityChildren = @[ native_root ];
551 self.flutterView.accessibilityChildren = nil;
555 #pragma mark - Private methods
557 - (BOOL)launchEngine {
558 if (![_engine runWithEntrypoint:nil]) {
568 - (void)listenForMetaModifiedKeyUpEvents {
569 if (_keyUpMonitor != nil) {
575 _keyUpMonitor = [NSEvent
576 addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
577 handler:^NSEvent*(NSEvent* event) {
580 NSResponder* firstResponder = [[event window] firstResponder];
581 if (weakSelf.viewLoaded && weakSelf.flutterView &&
582 (firstResponder == weakSelf.flutterView ||
583 firstResponder == weakSelf.textInputPlugin) &&
584 ([event modifierFlags] & NSEventModifierFlagCommand) &&
585 ([event type] == NSEventTypeKeyUp)) {
586 [weakSelf keyUp:event];
592 - (void)configureTrackingArea {
593 if (!
self.viewLoaded) {
598 if (_mouseTrackingMode != kFlutterMouseTrackingModeNone &&
self.flutterView) {
599 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
600 NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
601 switch (_mouseTrackingMode) {
602 case kFlutterMouseTrackingModeInKeyWindow:
603 options |= NSTrackingActiveInKeyWindow;
605 case kFlutterMouseTrackingModeInActiveApp:
606 options |= NSTrackingActiveInActiveApp;
608 case kFlutterMouseTrackingModeAlways:
609 options |= NSTrackingActiveAlways;
612 NSLog(
@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
615 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
619 [
self.flutterView addTrackingArea:_trackingArea];
620 }
else if (_trackingArea) {
621 [
self.flutterView removeTrackingArea:_trackingArea];
626 - (void)initializeKeyboard {
632 - (void)dispatchMouseEvent:(nonnull NSEvent*)event {
633 FlutterPointerPhase phase = _mouseState.buttons == 0
634 ? (_mouseState.flutter_state_is_down ? kUp : kHover)
635 : (_mouseState.flutter_state_is_down ? kMove : kDown);
636 [
self dispatchMouseEvent:event phase:phase];
639 - (void)dispatchGestureEvent:(nonnull NSEvent*)event {
640 if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) {
641 [
self dispatchMouseEvent:event phase:kPanZoomStart];
642 }
else if (event.phase == NSEventPhaseChanged) {
643 [
self dispatchMouseEvent:event phase:kPanZoomUpdate];
644 }
else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {
645 [
self dispatchMouseEvent:event phase:kPanZoomEnd];
646 }
else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
647 [
self dispatchMouseEvent:event phase:kHover];
652 if (event.momentumPhase == NSEventPhaseChanged) {
653 _mouseState.last_scroll_momentum_changed_time =
event.timestamp;
656 NSAssert(event.momentumPhase != NSEventPhaseNone,
657 @"Received gesture event with unexpected phase");
661 - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
662 NSAssert(
self.viewLoaded,
@"View must be loaded before it handles the mouse event");
666 if (_mouseState.flutter_state_is_added && phase == kAdd) {
672 if (phase == kPanZoomStart || phase == kPanZoomEnd) {
673 if (event.type == NSEventTypeScrollWheel) {
674 _mouseState.pan_gesture_phase =
event.phase;
675 }
else if (event.type == NSEventTypeMagnify) {
676 _mouseState.scale_gesture_phase =
event.phase;
677 }
else if (event.type == NSEventTypeRotate) {
678 _mouseState.rotate_gesture_phase =
event.phase;
681 if (phase == kPanZoomStart) {
682 if (event.type == NSEventTypeScrollWheel) {
684 _mouseState.last_scroll_momentum_changed_time = 0;
686 if (_mouseState.flutter_state_is_pan_zoom_started) {
690 _mouseState.flutter_state_is_pan_zoom_started =
true;
692 if (phase == kPanZoomEnd) {
693 if (!_mouseState.flutter_state_is_pan_zoom_started) {
697 NSAssert(event.phase == NSEventPhaseCancelled,
698 @"Received gesture event with unexpected phase");
702 NSEventPhase all_gestures_fields = _mouseState.pan_gesture_phase |
703 _mouseState.scale_gesture_phase |
704 _mouseState.rotate_gesture_phase;
705 NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;
706 if ((all_gestures_fields & active_mask) != 0) {
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,
746 .view_id = static_cast<FlutterViewId>(_viewId),
749 if (phase == kPanZoomUpdate) {
750 if (event.type == NSEventTypeScrollWheel) {
751 _mouseState.delta_x += event.scrollingDeltaX * self.flutterView.layer.contentsScale;
752 _mouseState.delta_y += event.scrollingDeltaY * self.flutterView.layer.contentsScale;
753 }
else if (event.type == NSEventTypeMagnify) {
754 _mouseState.scale += event.magnification;
755 }
else if (event.type == NSEventTypeRotate) {
756 _mouseState.rotation += event.rotation * (-M_PI / 180.0);
758 flutterEvent.pan_x = _mouseState.delta_x;
759 flutterEvent.pan_y = _mouseState.delta_y;
761 flutterEvent.scale = pow(2.0, _mouseState.scale);
762 flutterEvent.rotation = _mouseState.rotation;
763 }
else if (phase == kPanZoomEnd) {
764 _mouseState.GestureReset();
765 }
else if (phase != kPanZoomStart && event.type == NSEventTypeScrollWheel) {
766 flutterEvent.signal_kind = kFlutterPointerSignalKindScroll;
768 double pixelsPerLine = 1.0;
769 if (!event.hasPreciseScrollingDeltas) {
775 pixelsPerLine = 40.0;
777 double scaleFactor =
self.flutterView.layer.contentsScale;
786 double scaledDeltaX = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
787 double scaledDeltaY = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
788 if (event.modifierFlags & NSShiftKeyMask) {
789 flutterEvent.scroll_delta_x = scaledDeltaY;
790 flutterEvent.scroll_delta_y = scaledDeltaX;
792 flutterEvent.scroll_delta_x = scaledDeltaX;
793 flutterEvent.scroll_delta_y = scaledDeltaY;
797 [_keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
798 [_engine sendPointerEvent:flutterEvent];
801 if (phase == kDown) {
802 _mouseState.flutter_state_is_down =
true;
803 }
else if (phase == kUp) {
804 _mouseState.flutter_state_is_down =
false;
805 if (_mouseState.has_pending_exit) {
806 [
self dispatchMouseEvent:event phase:kRemove];
807 _mouseState.has_pending_exit =
false;
809 }
else if (phase == kAdd) {
810 _mouseState.flutter_state_is_added =
true;
811 }
else if (phase == kRemove) {
816 - (void)onAccessibilityStatusChanged:(BOOL)enabled {
817 if (!enabled &&
self.viewLoaded && [_textInputPlugin isFirstResponder]) {
822 [
self.view addSubview:_textInputPlugin];
826 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
828 return std::make_shared<flutter::AccessibilityBridgeMac>(engine,
self);
831 - (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(
id<MTLDevice>)device
832 commandQueue:(
id<MTLCommandQueue>)commandQueue {
833 return [[
FlutterView alloc] initWithMTLDevice:device
834 commandQueue:commandQueue
836 threadSynchronizer:_threadSynchronizer
840 - (void)onKeyboardLayoutChanged {
841 _keyboardLayoutData = nil;
847 - (NSString*)lookupKeyForAsset:(NSString*)asset {
851 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
855 #pragma mark - FlutterViewDelegate
860 - (void)viewDidReshape:(NSView*)view {
861 FML_DCHECK(view == _flutterView);
862 [_engine updateWindowMetricsForViewController:self];
865 - (BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
866 FML_DCHECK(view == _flutterView);
870 return !_textInputPlugin.isFirstResponder;
873 #pragma mark - FlutterPluginRegistry
876 return [_engine registrarForPlugin:pluginName];
879 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
880 return [_engine valuePublishedByPlugin:pluginKey];
883 #pragma mark - FlutterKeyboardViewDelegate
892 static NSData* CurrentKeyboardLayoutData() {
893 TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();
894 CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
895 if (layout_data == nil) {
900 source = TISCopyCurrentKeyboardLayoutInputSource();
901 layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
903 return (__bridge_transfer NSData*)CFRetain(layout_data);
906 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
907 callback:(nullable FlutterKeyEventCallback)callback
908 userData:(nullable
void*)userData {
909 [_engine sendKeyEvent:event callback:callback userData:userData];
913 return _engine.binaryMessenger;
916 - (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {
917 return [_textInputPlugin handleKeyEvent:event];
924 - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
925 if (_keyboardLayoutData == nil) {
926 _keyboardLayoutData = CurrentKeyboardLayoutData();
928 const UCKeyboardLayout* layout =
reinterpret_cast<const UCKeyboardLayout*
>(
929 CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));
931 UInt32 deadKeyState = 0;
932 UniCharCount stringLength = 0;
935 UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;
936 UInt32 keyboardType = LMGetKbdLast();
938 bool isDeadKey =
false;
940 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
941 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
943 if (status == noErr && stringLength == 0 && deadKeyState != 0) {
946 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
947 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
950 if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {
951 return LayoutClue{resultChar, isDeadKey};
953 return LayoutClue{0,
false};
956 - (nonnull NSDictionary*)getPressedState {
957 return [_keyboardManager getPressedState];
960 #pragma mark - NSResponder
962 - (BOOL)acceptsFirstResponder {
966 - (void)keyDown:(NSEvent*)event {
967 [_keyboardManager handleEvent:event];
970 - (void)keyUp:(NSEvent*)event {
971 [_keyboardManager handleEvent:event];
974 - (void)flagsChanged:(NSEvent*)event {
975 [_keyboardManager handleEvent:event];
978 - (void)mouseEntered:(NSEvent*)event {
979 if (_mouseState.has_pending_exit) {
980 _mouseState.has_pending_exit =
false;
982 [
self dispatchMouseEvent:event phase:kAdd];
986 - (void)mouseExited:(NSEvent*)event {
987 if (_mouseState.buttons != 0) {
988 _mouseState.has_pending_exit =
true;
991 [
self dispatchMouseEvent:event phase:kRemove];
994 - (void)mouseDown:(NSEvent*)event {
995 _mouseState.buttons |= kFlutterPointerButtonMousePrimary;
996 [
self dispatchMouseEvent:event];
999 - (void)mouseUp:(NSEvent*)event {
1000 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMousePrimary);
1001 [
self dispatchMouseEvent:event];
1004 - (void)mouseDragged:(NSEvent*)event {
1005 [
self dispatchMouseEvent:event];
1008 - (void)rightMouseDown:(NSEvent*)event {
1009 _mouseState.buttons |= kFlutterPointerButtonMouseSecondary;
1010 [
self dispatchMouseEvent:event];
1013 - (void)rightMouseUp:(NSEvent*)event {
1014 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMouseSecondary);
1015 [
self dispatchMouseEvent:event];
1018 - (void)rightMouseDragged:(NSEvent*)event {
1019 [
self dispatchMouseEvent:event];
1022 - (void)otherMouseDown:(NSEvent*)event {
1023 _mouseState.buttons |= (1 <<
event.buttonNumber);
1024 [
self dispatchMouseEvent:event];
1027 - (void)otherMouseUp:(NSEvent*)event {
1028 _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
1029 [
self dispatchMouseEvent:event];
1032 - (void)otherMouseDragged:(NSEvent*)event {
1033 [
self dispatchMouseEvent:event];
1036 - (void)mouseMoved:(NSEvent*)event {
1037 [
self dispatchMouseEvent:event];
1040 - (void)scrollWheel:(NSEvent*)event {
1041 [
self dispatchGestureEvent:event];
1044 - (void)magnifyWithEvent:(NSEvent*)event {
1045 [
self dispatchGestureEvent:event];
1048 - (void)rotateWithEvent:(NSEvent*)event {
1049 [
self dispatchGestureEvent:event];
1052 - (void)swipeWithEvent:(NSEvent*)event {
1056 - (void)touchesBeganWithEvent:(NSEvent*)event {
1057 NSTouch* touch =
event.allTouches.anyObject;
1059 if ((event.timestamp - _mouseState.last_scroll_momentum_changed_time) <
1060 kTrackpadTouchInertiaCancelWindowMs) {
1063 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
1064 NSPoint locationInBackingCoordinates =
1065 [
self.flutterView convertPointToBacking:locationInView];
1066 FlutterPointerEvent flutterEvent = {
1067 .struct_size =
sizeof(flutterEvent),
1068 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
1069 .x = locationInBackingCoordinates.x,
1070 .y = -locationInBackingCoordinates.y,
1071 .device = kPointerPanZoomDeviceId,
1072 .signal_kind = kFlutterPointerSignalKindScrollInertiaCancel,
1073 .device_kind = kFlutterPointerDeviceKindTrackpad,
1077 [_engine sendPointerEvent:flutterEvent];
1079 _mouseState.last_scroll_momentum_changed_time = 0;