8#include <Carbon/Carbon.h>
9#import <objc/message.h>
24#pragma mark - Static types and data.
36static 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;
137#pragma mark - Private interface declaration.
154- (void)setBackgroundColor:(NSColor*)color;
166@property(nonatomic) NSTrackingArea* trackingArea;
171@property(nonatomic) MouseState mouseState;
176@property(nonatomic)
id keyUpMonitor;
187- (void)configureTrackingArea;
194- (void)dispatchMouseEvent:(nonnull NSEvent*)event;
199- (void)dispatchGestureEvent:(nonnull NSEvent*)event;
208#pragma mark - FlutterViewWrapper implementation.
215- (instancetype)initWithFlutterView:(
FlutterView*)view
217 self = [
super initWithFrame:NSZeroRect];
221 view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
222 [
self addSubview:view];
227- (void)setBackgroundColor:(NSColor*)color {
228 [_flutterView setBackgroundColor:color];
231- (
BOOL)performKeyEquivalent:(NSEvent*)event {
238 if (
self.window.firstResponder != _flutterView || [
_controller isDispatchingKeyEvent:event]) {
239 return [
super performKeyEquivalent:event];
241 [_flutterView keyDown:event];
245- (NSArray*)accessibilityChildren {
246 return @[ _flutterView ];
251- (void)mouseDown:(NSEvent*)event {
252 if (@available(macOS 13.3.1, *)) {
253 [
super mouseDown:event];
265 [
self.nextResponder mouseDown:event];
271- (void)mouseUp:(NSEvent*)event {
272 if (@available(macOS 13.3.1, *)) {
273 [
super mouseUp:event];
285 [
self.nextResponder mouseUp:event];
291#pragma mark - FlutterViewController implementation.
297 std::shared_ptr<flutter::AccessibilityBridgeMac>
_bridge;
301@synthesize viewIdentifier = _viewIdentifier;
303@dynamic accessibilityBridge;
309 if (_engine.textInputPlugin.currentViewController ==
self) {
310 return _engine.textInputPlugin;
322 project:controller->_project
323 allowHeadlessExecution:NO];
325 NSCAssert(controller.
engine == nil,
326 @"The FlutterViewController is unexpectedly attached to "
327 @"engine %@ before initialization.",
329 [engine addViewController:controller];
331 NSCAssert(controller.
engine != nil,
332 @"The FlutterViewController unexpectedly stays unattached after initialization. "
333 @"In unit tests, this is likely because either the FlutterViewController or "
334 @"the FlutterEngine is mocked. Please subclass these classes instead.",
336 controller->_mouseTrackingMode = kFlutterMouseTrackingModeInKeyWindow;
337 [controller notifySemanticsEnabledChanged];
341 self = [
super initWithCoder:coder];
342 NSAssert(
self,
@"Super init cannot be nil");
344 CommonInit(
self, nil);
348- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
349 self = [
super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
350 NSAssert(
self,
@"Super init cannot be nil");
352 CommonInit(
self, nil);
357 self = [
super initWithNibName:nil bundle:nil];
358 NSAssert(
self,
@"Super init cannot be nil");
361 CommonInit(
self, nil);
366 nibName:(nullable NSString*)nibName
367 bundle:(nullable NSBundle*)nibBundle {
368 NSAssert(
engine != nil,
@"Engine is required");
370 self = [
super initWithNibName:nibName bundle:nibBundle];
378- (
BOOL)isDispatchingKeyEvent:(NSEvent*)event {
379 return [_engine.keyboardManager isDispatchingKeyEvent:event];
384 id<MTLDevice>
device = _engine.renderer.device;
385 id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;
386 if (!
device || !commandQueue) {
387 NSLog(
@"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.");
390 flutterView = [
self createFlutterViewWithMTLDevice:device commandQueue:commandQueue];
391 if (_backgroundColor != nil) {
396 self.view = wrapperView;
397 _flutterView = flutterView;
401 [
self configureTrackingArea];
402 [
self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];
403 [
self.view setWantsRestingTouches:YES];
404 [_engine viewControllerViewDidLoad:self];
407- (void)viewWillAppear {
408 [
super viewWillAppear];
409 if (!_engine.running) {
412 [
self listenForMetaModifiedKeyUpEvents];
415- (void)viewWillDisappear {
418 [NSEvent removeMonitor:_keyUpMonitor];
423 if ([
self attached]) {
424 [_engine removeViewController:self];
426 [
self.flutterView shutDown];
433#pragma mark - Public methods
435- (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
436 if (_mouseTrackingMode == mode) {
439 _mouseTrackingMode =
mode;
440 [
self configureTrackingArea];
443- (void)setBackgroundColor:(NSColor*)color {
444 _backgroundColor = color;
445 [_flutterView setBackgroundColor:_backgroundColor];
449 NSAssert([
self attached],
@"This view controller is not attached.");
450 return _viewIdentifier;
453- (void)onPreEngineRestart {
456- (void)notifySemanticsEnabledChanged {
458 BOOL newSemanticsEnabled = _engine.semanticsEnabled;
459 if (newSemanticsEnabled == mySemanticsEnabled) {
462 if (newSemanticsEnabled) {
463 _bridge = [
self createAccessibilityBridgeWithEngine:_engine];
466 _flutterView.accessibilityChildren = nil;
469 NSAssert(newSemanticsEnabled == !!
_bridge,
@"Failed to update semantics for the view.");
472- (
std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
478 NSAssert(_engine == nil,
@"Already attached to an engine %@.", _engine);
480 _viewIdentifier = viewIdentifier;
483- (void)detachFromEngine {
484 NSAssert(_engine != nil,
@"Not attached to any engine.");
489 return _engine != nil;
495 if (!_engine.semanticsEnabled) {
500 _bridge->AddFlutterSemanticsNodeUpdate(*node);
511 if (!
self.viewLoaded) {
515 auto root =
_bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
517 if ([
self.flutterView.accessibilityChildren count] == 0) {
518 NSAccessibilityElement* native_root = root->GetNativeViewAccessible();
519 self.flutterView.accessibilityChildren = @[ native_root ];
522 self.flutterView.accessibilityChildren = nil;
526#pragma mark - Private methods
528- (
BOOL)launchEngine {
529 if (![_engine runWithEntrypoint:nil]) {
539- (void)listenForMetaModifiedKeyUpEvents {
540 if (_keyUpMonitor != nil) {
546 _keyUpMonitor = [NSEvent
547 addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
548 handler:^NSEvent*(NSEvent* event) {
551 NSResponder* firstResponder = [[event window] firstResponder];
552 if (weakSelf.viewLoaded && weakSelf.flutterView &&
553 (firstResponder == weakSelf.flutterView ||
554 firstResponder == weakSelf.activeTextInputPlugin) &&
555 ([event modifierFlags] & NSEventModifierFlagCommand) &&
556 ([event type] == NSEventTypeKeyUp)) {
557 [weakSelf keyUp:event];
563- (void)configureTrackingArea {
564 if (!
self.viewLoaded) {
569 if (_mouseTrackingMode != kFlutterMouseTrackingModeNone &&
self.flutterView) {
570 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
571 NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;
572 switch (_mouseTrackingMode) {
573 case kFlutterMouseTrackingModeInKeyWindow:
574 options |= NSTrackingActiveInKeyWindow;
576 case kFlutterMouseTrackingModeInActiveApp:
577 options |= NSTrackingActiveInActiveApp;
579 case kFlutterMouseTrackingModeAlways:
580 options |= NSTrackingActiveAlways;
583 NSLog(
@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
586 _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
590 [
self.flutterView addTrackingArea:_trackingArea];
591 }
else if (_trackingArea) {
592 [
self.flutterView removeTrackingArea:_trackingArea];
597- (void)dispatchMouseEvent:(nonnull NSEvent*)event {
601 [
self dispatchMouseEvent:event phase:phase];
604- (void)dispatchGestureEvent:(nonnull NSEvent*)event {
605 if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) {
606 [
self dispatchMouseEvent:event phase:kPanZoomStart];
607 }
else if (event.phase == NSEventPhaseChanged) {
608 [
self dispatchMouseEvent:event phase:kPanZoomUpdate];
609 }
else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {
610 [
self dispatchMouseEvent:event phase:kPanZoomEnd];
611 }
else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
612 [
self dispatchMouseEvent:event phase:kHover];
617 if (event.momentumPhase == NSEventPhaseChanged) {
618 _mouseState.last_scroll_momentum_changed_time =
event.timestamp;
621 NSAssert(event.momentumPhase != NSEventPhaseNone,
622 @"Received gesture event with unexpected phase");
627 NSAssert(
self.viewLoaded,
@"View must be loaded before it handles the mouse event");
638 if (event.type == NSEventTypeScrollWheel) {
640 }
else if (event.type == NSEventTypeMagnify) {
642 }
else if (event.type == NSEventTypeRotate) {
647 if (event.type == NSEventTypeScrollWheel) {
651 if (
_mouseState.flutter_state_is_pan_zoom_started) {
655 _mouseState.flutter_state_is_pan_zoom_started =
true;
658 if (!
_mouseState.flutter_state_is_pan_zoom_started) {
662 NSAssert(event.phase == NSEventPhaseCancelled,
663 @"Received gesture event with unexpected phase");
667 NSEventPhase all_gestures_fields =
_mouseState.pan_gesture_phase |
670 NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;
671 if ((all_gestures_fields & active_mask) != 0) {
681 NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
682 location:event.locationInWindow
684 timestamp:event.timestamp
685 windowNumber:event.windowNumber
690 [
self dispatchMouseEvent:addEvent phase:kAdd];
693 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
694 NSPoint locationInBackingCoordinates = [
self.flutterView convertPointToBacking:locationInView];
704 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
705 .x = locationInBackingCoordinates.x,
706 .y = -locationInBackingCoordinates.
y,
708 .device_kind = deviceKind,
710 .buttons = phase ==
kAdd ? 0 : _mouseState.buttons,
711 .view_id = static_cast<FlutterViewIdentifier>(_viewIdentifier),
715 if (event.type == NSEventTypeScrollWheel) {
716 _mouseState.delta_x += event.scrollingDeltaX *
self.flutterView.layer.contentsScale;
717 _mouseState.delta_y += event.scrollingDeltaY *
self.flutterView.layer.contentsScale;
718 }
else if (event.type == NSEventTypeMagnify) {
720 }
else if (event.type == NSEventTypeRotate) {
721 _mouseState.rotation += event.rotation * (-M_PI / 180.0);
730 }
else if (phase !=
kPanZoomStart && event.type == NSEventTypeScrollWheel) {
733 double pixelsPerLine = 1.0;
734 if (!event.hasPreciseScrollingDeltas) {
740 pixelsPerLine = 40.0;
742 double scaleFactor =
self.flutterView.layer.contentsScale;
751 double scaledDeltaX = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
752 double scaledDeltaY = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
753 if (event.modifierFlags & NSShiftKeyMask) {
762 [_engine.keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
763 [_engine sendPointerEvent:flutterEvent];
766 if (phase ==
kDown) {
768 }
else if (phase ==
kUp) {
771 [
self dispatchMouseEvent:event phase:kRemove];
774 }
else if (phase ==
kAdd) {
781- (void)onAccessibilityStatusChanged:(
BOOL)enabled {
782 if (!enabled &&
self.viewLoaded && [
self.activeTextInputPlugin isFirstResponder]) {
787 [
self.view addSubview:self.activeTextInputPlugin];
791- (
std::shared_ptr<flutter::AccessibilityBridgeMac>)createAccessibilityBridgeWithEngine:
793 return std::make_shared<flutter::AccessibilityBridgeMac>(
engine,
self);
796- (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(
id<MTLDevice>)device
797 commandQueue:(
id<MTLCommandQueue>)commandQueue {
798 return [[
FlutterView alloc] initWithMTLDevice:device
799 commandQueue:commandQueue
801 viewIdentifier:_viewIdentifier];
804- (NSString*)lookupKeyForAsset:(NSString*)asset {
808- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
812#pragma mark - FlutterViewDelegate
817- (void)viewDidReshape:(NSView*)view {
819 [_engine updateWindowMetricsForViewController:self];
822- (
BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
827 return !
self.activeTextInputPlugin.isFirstResponder;
830#pragma mark - FlutterPluginRegistry
832- (
id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
833 return [_engine registrarForPlugin:pluginName];
836- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
837 return [_engine valuePublishedByPlugin:pluginKey];
840#pragma mark - FlutterKeyboardViewDelegate
842- (
BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {
843 return [
self.activeTextInputPlugin handleKeyEvent:event];
846#pragma mark - NSResponder
848- (
BOOL)acceptsFirstResponder {
852- (void)keyDown:(NSEvent*)event {
853 [_engine.keyboardManager handleEvent:event withContext:self];
856- (void)keyUp:(NSEvent*)event {
857 [_engine.keyboardManager handleEvent:event withContext:self];
860- (void)flagsChanged:(NSEvent*)event {
861 [_engine.keyboardManager handleEvent:event withContext:self];
864- (void)mouseEntered:(NSEvent*)event {
868 [
self dispatchMouseEvent:event phase:kAdd];
872- (void)mouseExited:(NSEvent*)event {
877 [
self dispatchMouseEvent:event phase:kRemove];
880- (void)mouseDown:(NSEvent*)event {
882 [
self dispatchMouseEvent:event];
885- (void)mouseUp:(NSEvent*)event {
887 [
self dispatchMouseEvent:event];
890- (void)mouseDragged:(NSEvent*)event {
891 [
self dispatchMouseEvent:event];
894- (void)rightMouseDown:(NSEvent*)event {
896 [
self dispatchMouseEvent:event];
899- (void)rightMouseUp:(NSEvent*)event {
901 [
self dispatchMouseEvent:event];
904- (void)rightMouseDragged:(NSEvent*)event {
905 [
self dispatchMouseEvent:event];
908- (void)otherMouseDown:(NSEvent*)event {
910 [
self dispatchMouseEvent:event];
913- (void)otherMouseUp:(NSEvent*)event {
914 _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
915 [
self dispatchMouseEvent:event];
918- (void)otherMouseDragged:(NSEvent*)event {
919 [
self dispatchMouseEvent:event];
922- (void)mouseMoved:(NSEvent*)event {
923 [
self dispatchMouseEvent:event];
926- (void)scrollWheel:(NSEvent*)event {
927 [
self dispatchGestureEvent:event];
930- (void)magnifyWithEvent:(NSEvent*)event {
931 [
self dispatchGestureEvent:event];
934- (void)rotateWithEvent:(NSEvent*)event {
935 [
self dispatchGestureEvent:event];
938- (void)swipeWithEvent:(NSEvent*)event {
942- (void)touchesBeganWithEvent:(NSEvent*)event {
943 NSTouch* touch =
event.allTouches.anyObject;
945 if ((event.timestamp -
_mouseState.last_scroll_momentum_changed_time) <
946 kTrackpadTouchInertiaCancelWindowMs) {
949 NSPoint locationInView = [
self.flutterView convertPoint:event.locationInWindow fromView:nil];
950 NSPoint locationInBackingCoordinates =
951 [
self.flutterView convertPointToBacking:locationInView];
954 .timestamp =
static_cast<size_t>(event.timestamp * USEC_PER_SEC),
955 .x = locationInBackingCoordinates.x,
956 .y = -locationInBackingCoordinates.
y,
963 [_engine sendPointerEvent:flutterEvent];
FlutterPointerPhase
The phase of the pointer event.
@ kPanZoomUpdate
The pan/zoom updated.
@ kHover
The pointer moved while up.
@ kPanZoomStart
A pan/zoom started on this pointer.
@ kPanZoomEnd
The pan/zoom ended.
@ kFlutterPointerButtonMousePrimary
@ kFlutterPointerButtonMouseSecondary
@ kFlutterPointerSignalKindScrollInertiaCancel
@ kFlutterPointerSignalKindScroll
FlutterPointerDeviceKind
The device type that created a pointer event.
@ kFlutterPointerDeviceKindTrackpad
@ kFlutterPointerDeviceKindMouse
static constexpr int32_t kPointerPanZoomDeviceId
static constexpr int32_t kMousePointerDeviceId
G_BEGIN_DECLS FlutterViewId view_id
#define FML_DCHECK(condition)
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
FlutterViewIdentifier viewIdentifier
void setBackgroundColor:(nonnull NSColor *color)
instancetype initWithCoder
FlutterDartProject * _project
__weak FlutterViewController * _controller
std::shared_ptr< flutter::AccessibilityBridgeMac > _bridge
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
void Reset(SkPathBuilder *path)
double scroll_delta_y
The y offset of the scroll in physical pixels.
size_t struct_size
The size of this struct. Must be sizeof(FlutterPointerEvent).
double scale
The scale of the pan/zoom, where 1.0 is the initial scale.
FlutterPointerSignalKind signal_kind
double rotation
The rotation of the pan/zoom in radians, where 0.0 is the initial angle.
double scroll_delta_x
The x offset of the scroll in physical pixels.
double pan_x
The x offset of the pan/zoom in physical pixels.
double pan_y
The y offset of the pan/zoom in physical pixels.
A batch of updates to semantics nodes and custom actions.
size_t node_count
The number of semantics node updates.
size_t custom_action_count
The number of semantics custom action updates.
FlutterSemanticsNode2 ** nodes
FlutterSemanticsCustomAction2 ** custom_actions