8 #include <Carbon/Carbon.h>
9 #import <objc/message.h>
11 #include "flutter/common/constants.h"
12 #include "flutter/fml/platform/darwin/cf_utils.h"
22 #include "flutter/shell/platform/embedder/embedder.h"
24 #pragma mark - Static types and data.
32 static constexpr int32_t kMousePointerDeviceId = 0;
33 static constexpr int32_t kPointerPanZoomDeviceId = 1;
38 static constexpr
double kTrackpadTouchInertiaCancelWindowMs = 0.050;
71 bool flutter_state_is_added =
false;
76 bool flutter_state_is_down =
false;
85 bool has_pending_exit =
false;
90 bool flutter_state_is_pan_zoom_started =
false;
95 NSEventPhase pan_gesture_phase = NSEventPhaseNone;
100 NSEventPhase scale_gesture_phase = NSEventPhaseNone;
105 NSEventPhase rotate_gesture_phase = NSEventPhaseNone;
110 NSTimeInterval last_scroll_momentum_changed_time = 0;
115 void GestureReset() {
120 flutter_state_is_pan_zoom_started =
false;
121 pan_gesture_phase = NSEventPhaseNone;
122 scale_gesture_phase = NSEventPhaseNone;
123 rotate_gesture_phase = NSEventPhaseNone;
130 flutter_state_is_added =
false;
131 flutter_state_is_down =
false;
132 has_pending_exit =
false;
139 #pragma mark - Private interface declaration.
156 - (void)setBackgroundColor:(NSColor*)color;
168 @property(nonatomic) NSTrackingArea* trackingArea;
173 @property(nonatomic) MouseState mouseState;
178 @property(nonatomic)
id keyUpMonitor;
189 @property(nonatomic) NSData* keyboardLayoutData;
194 - (BOOL)launchEngine;
200 - (void)configureTrackingArea;
205 - (void)initializeKeyboard;
212 - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
217 - (void)dispatchGestureEvent:(nonnull NSEvent*)event;
222 - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
230 - (void)onKeyboardLayoutChanged;
234 #pragma mark - FlutterViewWrapper implementation.
243 CFDictionaryRef userInfo) {
245 if (controller != nil) {
246 [controller onKeyboardLayoutChanged];
255 - (instancetype)initWithFlutterView:(
FlutterView*)view
257 self = [
super initWithFrame:NSZeroRect];
261 view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
262 [
self addSubview:view];
267 - (void)setBackgroundColor:(NSColor*)color {
268 [_flutterView setBackgroundColor:color];
271 - (BOOL)performKeyEquivalent:(NSEvent*)event {
278 if (
self.window.firstResponder != _flutterView || [
_controller isDispatchingKeyEvent:event]) {
279 return [
super performKeyEquivalent:event];
281 [_flutterView keyDown:event];
285 - (NSArray*)accessibilityChildren {
286 return @[ _flutterView ];
291 - (void)mouseDown:(NSEvent*)event {
292 if (@available(macOS 13.3.1, *)) {
293 [
super mouseDown:event];
305 [
self.nextResponder mouseDown:event];
311 - (void)mouseUp:(NSEvent*)event {
312 if (@available(macOS 13.3.1, *)) {
313 [
super mouseUp:event];
325 [
self.nextResponder mouseUp:event];
331 #pragma mark - FlutterViewController implementation.
337 std::shared_ptr<flutter::AccessibilityBridgeMac>
_bridge;
345 @synthesize viewIdentifier = _viewIdentifier;
347 @dynamic accessibilityBridge;
355 project:controller->_project
356 allowHeadlessExecution:NO];
358 NSCAssert(controller.
engine == nil,
359 @"The FlutterViewController is unexpectedly attached to "
360 @"engine %@ before initialization.",
362 [engine addViewController:controller];
363 NSCAssert(controller.
engine != nil,
364 @"The FlutterViewController unexpectedly stays unattached after initialization. "
365 @"In unit tests, this is likely because either the FlutterViewController or "
366 @"the FlutterEngine is mocked. Please subclass these classes instead.",
368 controller->_mouseTrackingMode = kFlutterMouseTrackingModeInKeyWindow;
370 [controller initializeKeyboard];
371 [controller notifySemanticsEnabledChanged];
373 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
376 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
377 CFNotificationSuspensionBehaviorDeliverImmediately);
380 - (instancetype)initWithCoder:(NSCoder*)coder {
381 self = [
super initWithCoder:coder];
382 NSAssert(
self,
@"Super init cannot be nil");
384 CommonInit(
self, nil);
388 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
389 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
390 NSAssert(
self,
@"Super init cannot be nil");
392 CommonInit(
self, nil);
397 self = [
super initWithNibName:nil bundle:nil];
398 NSAssert(
self,
@"Super init cannot be nil");
401 CommonInit(
self, nil);
406 nibName:(nullable NSString*)nibName
407 bundle:(nullable NSBundle*)nibBundle {
408 NSAssert(engine != nil,
@"Engine is required");
410 self = [
super initWithNibName:nibName bundle:nibBundle];
412 CommonInit(
self, engine);
418 - (BOOL)isDispatchingKeyEvent:(NSEvent*)event {
419 return [_keyboardManager isDispatchingKeyEvent:event];
424 id<MTLDevice> device = _engine.renderer.device;
425 id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;
426 if (!device || !commandQueue) {
427 NSLog(
@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
430 flutterView = [
self createFlutterViewWithMTLDevice:device commandQueue:commandQueue];
431 if (_backgroundColor != nil) {
436 self.view = wrapperView;
437 _flutterView = flutterView;
440 - (void)viewDidLoad {
441 [
self configureTrackingArea];
442 [
self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];
443 [
self.view setWantsRestingTouches:YES];
444 [_engine viewControllerViewDidLoad:self];
447 - (void)viewWillAppear {
448 [
super viewWillAppear];
449 if (!_engine.running) {
452 [
self listenForMetaModifiedKeyUpEvents];
455 - (void)viewWillDisappear {
458 [NSEvent removeMonitor:_keyUpMonitor];
463 if ([
self attached]) {
464 [_engine removeViewController:self];
466 CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
467 CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge
void*)
self);
470 #pragma mark - Public methods
472 - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
473 if (_mouseTrackingMode == mode) {
476 _mouseTrackingMode = mode;
477 [
self configureTrackingArea];
480 - (void)setBackgroundColor:(NSColor*)color {
481 _backgroundColor = color;
482 [_flutterView setBackgroundColor:_backgroundColor];
486 NSAssert([
self attached],
@"This view controller is not attached.");
487 return _viewIdentifier;
490 - (void)onPreEngineRestart {
491 [
self initializeKeyboard];
494 - (void)notifySemanticsEnabledChanged {
495 BOOL mySemanticsEnabled = !!
_bridge;
496 BOOL newSemanticsEnabled = _engine.semanticsEnabled;
497 if (newSemanticsEnabled == mySemanticsEnabled) {
500 if (newSemanticsEnabled) {
501 _bridge = [
self createAccessibilityBridgeWithEngine:_engine];
504 _flutterView.accessibilityChildren = nil;
507 NSAssert(newSemanticsEnabled == !!
_bridge,
@"Failed to update semantics for the view.");
510 - (std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
517 NSAssert(_engine == nil,
@"Already attached to an engine %@.", _engine);
519 _viewIdentifier = viewIdentifier;
521 [_threadSynchronizer registerView:_viewIdentifier];
524 - (void)detachFromEngine {
525 NSAssert(_engine != nil,
@"Not attached to any engine.");
526 [_threadSynchronizer deregisterView:_viewIdentifier];
532 return _engine != nil;
535 - (void)updateSemantics:(const FlutterSemanticsUpdate2*)update {
538 if (!_engine.semanticsEnabled) {
541 for (
size_t i = 0; i < update->node_count; i++) {
542 const FlutterSemanticsNode2* node = update->nodes[i];
543 _bridge->AddFlutterSemanticsNodeUpdate(*node);
546 for (
size_t i = 0; i < update->custom_action_count; i++) {
547 const FlutterSemanticsCustomAction2* action = update->custom_actions[i];
548 _bridge->AddFlutterSemanticsCustomActionUpdate(*action);
554 if (!
self.viewLoaded) {
558 auto root =
_bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
560 if ([
self.flutterView.accessibilityChildren count] == 0) {
561 NSAccessibilityElement* native_root = root->GetNativeViewAccessible();
562 self.flutterView.accessibilityChildren = @[ native_root ];
565 self.flutterView.accessibilityChildren = nil;
569 #pragma mark - Private methods
571 - (BOOL)launchEngine {
572 if (![_engine runWithEntrypoint:nil]) {
582 - (void)listenForMetaModifiedKeyUpEvents {
583 if (_keyUpMonitor != nil) {
589 _keyUpMonitor = [NSEvent
590 addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
591 handler:^NSEvent*(NSEvent* event) {
594 NSResponder* firstResponder = [[event window] firstResponder];
595 if (weakSelf.viewLoaded && weakSelf.flutterView &&
596 (firstResponder == weakSelf.flutterView ||
597 firstResponder == weakSelf.textInputPlugin) &&
598 ([event modifierFlags] & NSEventModifierFlagCommand) &&
599 ([event type] == NSEventTypeKeyUp)) {
600 [weakSelf keyUp:event];
606 - (void)configureTrackingArea {
607 if (!
self.viewLoaded) {
612 if (_mouseTrackingMode != kFlutterMouseTrackingModeNone &&
self.flutterView) {
613 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
614 NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
615 switch (_mouseTrackingMode) {
616 case kFlutterMouseTrackingModeInKeyWindow:
617 options |= NSTrackingActiveInKeyWindow;
619 case kFlutterMouseTrackingModeInActiveApp:
620 options |= NSTrackingActiveInActiveApp;
622 case kFlutterMouseTrackingModeAlways:
623 options |= NSTrackingActiveAlways;
626 NSLog(
@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
629 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
633 [
self.flutterView addTrackingArea:_trackingArea];
634 }
else if (_trackingArea) {
635 [
self.flutterView removeTrackingArea:_trackingArea];
640 - (void)initializeKeyboard {
646 - (void)dispatchMouseEvent:(nonnull NSEvent*)event {
647 FlutterPointerPhase phase = _mouseState.buttons == 0
648 ? (_mouseState.flutter_state_is_down ? kUp : kHover)
649 : (_mouseState.flutter_state_is_down ? kMove : kDown);
650 [
self dispatchMouseEvent:event phase:phase];
653 - (void)dispatchGestureEvent:(nonnull NSEvent*)event {
654 if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) {
655 [
self dispatchMouseEvent:event phase:kPanZoomStart];
656 }
else if (event.phase == NSEventPhaseChanged) {
657 [
self dispatchMouseEvent:event phase:kPanZoomUpdate];
658 }
else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {
659 [
self dispatchMouseEvent:event phase:kPanZoomEnd];
660 }
else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
661 [
self dispatchMouseEvent:event phase:kHover];
666 if (event.momentumPhase == NSEventPhaseChanged) {
667 _mouseState.last_scroll_momentum_changed_time =
event.timestamp;
670 NSAssert(event.momentumPhase != NSEventPhaseNone,
671 @"Received gesture event with unexpected phase");
675 - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
676 NSAssert(
self.viewLoaded,
@"View must be loaded before it handles the mouse event");
680 if (_mouseState.flutter_state_is_added && phase == kAdd) {
686 if (phase == kPanZoomStart || phase == kPanZoomEnd) {
687 if (event.type == NSEventTypeScrollWheel) {
688 _mouseState.pan_gesture_phase =
event.phase;
689 }
else if (event.type == NSEventTypeMagnify) {
690 _mouseState.scale_gesture_phase =
event.phase;
691 }
else if (event.type == NSEventTypeRotate) {
692 _mouseState.rotate_gesture_phase =
event.phase;
695 if (phase == kPanZoomStart) {
696 if (event.type == NSEventTypeScrollWheel) {
698 _mouseState.last_scroll_momentum_changed_time = 0;
700 if (_mouseState.flutter_state_is_pan_zoom_started) {
704 _mouseState.flutter_state_is_pan_zoom_started =
true;
706 if (phase == kPanZoomEnd) {
707 if (!_mouseState.flutter_state_is_pan_zoom_started) {
711 NSAssert(event.phase == NSEventPhaseCancelled,
712 @"Received gesture event with unexpected phase");
716 NSEventPhase all_gestures_fields = _mouseState.pan_gesture_phase |
717 _mouseState.scale_gesture_phase |
718 _mouseState.rotate_gesture_phase;
719 NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;
720 if ((all_gestures_fields & active_mask) != 0) {
728 if (!_mouseState.flutter_state_is_added && phase != kAdd) {
730 NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
731 location:event.locationInWindow
733 timestamp:event.timestamp
734 windowNumber:event.windowNumber
739 [
self dispatchMouseEvent:addEvent phase:kAdd];
742 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
743 NSPoint locationInBackingCoordinates = [
self.flutterView convertPointToBacking:locationInView];
744 int32_t device = kMousePointerDeviceId;
745 FlutterPointerDeviceKind deviceKind = kFlutterPointerDeviceKindMouse;
746 if (phase == kPanZoomStart || phase == kPanZoomUpdate || phase == kPanZoomEnd) {
747 device = kPointerPanZoomDeviceId;
748 deviceKind = kFlutterPointerDeviceKindTrackpad;
750 FlutterPointerEvent flutterEvent = {
751 .struct_size =
sizeof(flutterEvent),
753 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
754 .x = locationInBackingCoordinates.x,
755 .y = -locationInBackingCoordinates.y,
757 .device_kind = deviceKind,
759 .buttons = phase == kAdd ? 0 : _mouseState.buttons,
760 .view_id = static_cast<FlutterViewIdentifier>(_viewIdentifier),
763 if (phase == kPanZoomUpdate) {
764 if (event.type == NSEventTypeScrollWheel) {
765 _mouseState.delta_x += event.scrollingDeltaX * self.flutterView.layer.contentsScale;
766 _mouseState.delta_y += event.scrollingDeltaY * self.flutterView.layer.contentsScale;
767 }
else if (event.type == NSEventTypeMagnify) {
768 _mouseState.scale += event.magnification;
769 }
else if (event.type == NSEventTypeRotate) {
770 _mouseState.rotation += event.rotation * (-M_PI / 180.0);
772 flutterEvent.pan_x = _mouseState.delta_x;
773 flutterEvent.pan_y = _mouseState.delta_y;
775 flutterEvent.scale = pow(2.0, _mouseState.scale);
776 flutterEvent.rotation = _mouseState.rotation;
777 }
else if (phase == kPanZoomEnd) {
778 _mouseState.GestureReset();
779 }
else if (phase != kPanZoomStart && event.type == NSEventTypeScrollWheel) {
780 flutterEvent.signal_kind = kFlutterPointerSignalKindScroll;
782 double pixelsPerLine = 1.0;
783 if (!event.hasPreciseScrollingDeltas) {
789 pixelsPerLine = 40.0;
791 double scaleFactor =
self.flutterView.layer.contentsScale;
800 double scaledDeltaX = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
801 double scaledDeltaY = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
802 if (event.modifierFlags & NSShiftKeyMask) {
803 flutterEvent.scroll_delta_x = scaledDeltaY;
804 flutterEvent.scroll_delta_y = scaledDeltaX;
806 flutterEvent.scroll_delta_x = scaledDeltaX;
807 flutterEvent.scroll_delta_y = scaledDeltaY;
811 [_keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
812 [_engine sendPointerEvent:flutterEvent];
815 if (phase == kDown) {
816 _mouseState.flutter_state_is_down =
true;
817 }
else if (phase == kUp) {
818 _mouseState.flutter_state_is_down =
false;
819 if (_mouseState.has_pending_exit) {
820 [
self dispatchMouseEvent:event phase:kRemove];
821 _mouseState.has_pending_exit =
false;
823 }
else if (phase == kAdd) {
824 _mouseState.flutter_state_is_added =
true;
825 }
else if (phase == kRemove) {
830 - (void)onAccessibilityStatusChanged:(BOOL)enabled {
831 if (!enabled &&
self.viewLoaded && [_textInputPlugin isFirstResponder]) {
836 [
self.view addSubview:_textInputPlugin];
840 - (std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
842 return std::make_shared<flutter::AccessibilityBridgeMac>(engine,
self);
845 - (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(
id<MTLDevice>)device
846 commandQueue:(
id<MTLCommandQueue>)commandQueue {
847 return [[
FlutterView alloc] initWithMTLDevice:device
848 commandQueue:commandQueue
850 threadSynchronizer:_threadSynchronizer
851 viewIdentifier:_viewIdentifier];
854 - (void)onKeyboardLayoutChanged {
855 _keyboardLayoutData = nil;
861 - (NSString*)lookupKeyForAsset:(NSString*)asset {
865 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
869 #pragma mark - FlutterViewDelegate
874 - (void)viewDidReshape:(NSView*)view {
875 FML_DCHECK(view == _flutterView);
876 [_engine updateWindowMetricsForViewController:self];
879 - (BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
880 FML_DCHECK(view == _flutterView);
884 return !_textInputPlugin.isFirstResponder;
887 #pragma mark - FlutterPluginRegistry
890 return [_engine registrarForPlugin:pluginName];
893 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
894 return [_engine valuePublishedByPlugin:pluginKey];
897 #pragma mark - FlutterKeyboardViewDelegate
906 static NSData* CurrentKeyboardLayoutData() {
907 fml::CFRef<TISInputSourceRef> source(TISCopyCurrentKeyboardInputSource());
908 CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
909 if (layout_data == nil) {
913 source.Reset(TISCopyCurrentKeyboardLayoutInputSource());
914 layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
916 return (__bridge NSData*)layout_data;
919 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
920 callback:(nullable FlutterKeyEventCallback)callback
921 userData:(nullable
void*)userData {
922 [_engine sendKeyEvent:event callback:callback userData:userData];
926 return _engine.binaryMessenger;
929 - (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {
930 return [_textInputPlugin handleKeyEvent:event];
937 - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
938 if (_keyboardLayoutData == nil) {
939 _keyboardLayoutData = CurrentKeyboardLayoutData();
941 const UCKeyboardLayout* layout =
reinterpret_cast<const UCKeyboardLayout*
>(
942 CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));
944 UInt32 deadKeyState = 0;
945 UniCharCount stringLength = 0;
948 UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;
949 UInt32 keyboardType = LMGetKbdLast();
951 bool isDeadKey =
false;
953 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
954 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
956 if (status == noErr && stringLength == 0 && deadKeyState != 0) {
959 UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
960 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
963 if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {
964 return LayoutClue{resultChar, isDeadKey};
966 return LayoutClue{0,
false};
969 - (nonnull NSDictionary*)getPressedState {
970 return [_keyboardManager getPressedState];
973 #pragma mark - NSResponder
975 - (BOOL)acceptsFirstResponder {
979 - (void)keyDown:(NSEvent*)event {
980 [_keyboardManager handleEvent:event];
983 - (void)keyUp:(NSEvent*)event {
984 [_keyboardManager handleEvent:event];
987 - (void)flagsChanged:(NSEvent*)event {
988 [_keyboardManager handleEvent:event];
991 - (void)mouseEntered:(NSEvent*)event {
992 if (_mouseState.has_pending_exit) {
993 _mouseState.has_pending_exit =
false;
995 [
self dispatchMouseEvent:event phase:kAdd];
999 - (void)mouseExited:(NSEvent*)event {
1000 if (_mouseState.buttons != 0) {
1001 _mouseState.has_pending_exit =
true;
1004 [
self dispatchMouseEvent:event phase:kRemove];
1007 - (void)mouseDown:(NSEvent*)event {
1008 _mouseState.buttons |= kFlutterPointerButtonMousePrimary;
1009 [
self dispatchMouseEvent:event];
1012 - (void)mouseUp:(NSEvent*)event {
1013 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMousePrimary);
1014 [
self dispatchMouseEvent:event];
1017 - (void)mouseDragged:(NSEvent*)event {
1018 [
self dispatchMouseEvent:event];
1021 - (void)rightMouseDown:(NSEvent*)event {
1022 _mouseState.buttons |= kFlutterPointerButtonMouseSecondary;
1023 [
self dispatchMouseEvent:event];
1026 - (void)rightMouseUp:(NSEvent*)event {
1027 _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMouseSecondary);
1028 [
self dispatchMouseEvent:event];
1031 - (void)rightMouseDragged:(NSEvent*)event {
1032 [
self dispatchMouseEvent:event];
1035 - (void)otherMouseDown:(NSEvent*)event {
1036 _mouseState.buttons |= (1 <<
event.buttonNumber);
1037 [
self dispatchMouseEvent:event];
1040 - (void)otherMouseUp:(NSEvent*)event {
1041 _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
1042 [
self dispatchMouseEvent:event];
1045 - (void)otherMouseDragged:(NSEvent*)event {
1046 [
self dispatchMouseEvent:event];
1049 - (void)mouseMoved:(NSEvent*)event {
1050 [
self dispatchMouseEvent:event];
1053 - (void)scrollWheel:(NSEvent*)event {
1054 [
self dispatchGestureEvent:event];
1057 - (void)magnifyWithEvent:(NSEvent*)event {
1058 [
self dispatchGestureEvent:event];
1061 - (void)rotateWithEvent:(NSEvent*)event {
1062 [
self dispatchGestureEvent:event];
1065 - (void)swipeWithEvent:(NSEvent*)event {
1069 - (void)touchesBeganWithEvent:(NSEvent*)event {
1070 NSTouch* touch =
event.allTouches.anyObject;
1072 if ((event.timestamp - _mouseState.last_scroll_momentum_changed_time) <
1073 kTrackpadTouchInertiaCancelWindowMs) {
1076 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
1077 NSPoint locationInBackingCoordinates =
1078 [
self.flutterView convertPointToBacking:locationInView];
1079 FlutterPointerEvent flutterEvent = {
1080 .struct_size =
sizeof(flutterEvent),
1081 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
1082 .x = locationInBackingCoordinates.x,
1083 .y = -locationInBackingCoordinates.y,
1084 .device = kPointerPanZoomDeviceId,
1085 .signal_kind = kFlutterPointerSignalKindScrollInertiaCancel,
1086 .device_kind = kFlutterPointerDeviceKindTrackpad,
1090 [_engine sendPointerEvent:flutterEvent];
1092 _mouseState.last_scroll_momentum_changed_time = 0;