1616
1717static const int kDefaultWindowFramebuffer = 0 ;
1818
19+ namespace {
20+
21+ /* *
22+ * State tracking for mouse events, to adapt between the events coming from the system and the
23+ * events that the embedding API expects.
24+ */
25+ struct MouseState {
26+ /* *
27+ * Whether or not a kAdd event has been sent (or sent again since the last kRemove if tracking is
28+ * enabled). Used to determine whether to send a kAdd event before sending an incoming mouse
29+ * event, since Flutter expects pointers to be added before events are sent for them.
30+ */
31+ bool flutter_state_is_added = false ;
32+
33+ /* *
34+ * Whether or not a kDown has been sent since the last kAdd/kUp.
35+ */
36+ bool flutter_state_is_down = false ;
37+
38+ /* *
39+ * Whether or not mouseExited: was received while a button was down. Cocoa's behavior when
40+ * dragging out of a tracked area is to send an exit, then keep sending drag events until the last
41+ * button is released. If it was released inside the view, mouseEntered: is sent the next time the
42+ * mouse moves. Flutter doesn't expect to receive events after a kRemove, so the kRemove for the
43+ * exit needs to be delayed until after the last mouse button is released.
44+ */
45+ bool has_pending_exit = false ;
46+
47+ /* *
48+ * The currently pressed buttons, as represented in FlutterPointerEvent.
49+ */
50+ int64_t buttons = 0 ;
51+
52+ /* *
53+ * Resets all state to default values.
54+ */
55+ void Reset () {
56+ flutter_state_is_added = false ;
57+ flutter_state_is_down = false ;
58+ has_pending_exit = false ;
59+ buttons = 0 ;
60+ }
61+ };
62+
63+ } // namespace
64+
1965#pragma mark - Private interface declaration.
2066
2167/* *
@@ -34,12 +80,9 @@ @interface FLEViewController ()
3480@property (nonatomic ) NSTrackingArea * trackingArea;
3581
3682/* *
37- * Whether or not a kAdd event has been sent for the mouse (or sent again since
38- * the last kRemove was sent if tracking is enabled). Used to determine whether
39- * to send an Add event before sending an incoming mouse event, since Flutter
40- * expects a pointers to be added before events are sent for them.
83+ * The current state of the mouse and the sent mouse events.
4184 */
42- @property (nonatomic ) BOOL mouseCurrentlyAdded ;
85+ @property (nonatomic ) MouseState mouseState ;
4386
4487/* *
4588 * Updates |trackingArea| for the current tracking settings, creating it with
@@ -81,6 +124,13 @@ - (void)makeResourceContextCurrent;
81124 */
82125- (void )handlePlatformMessage : (const FlutterPlatformMessage*)message ;
83126
127+ /* *
128+ * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.
129+ *
130+ * mouseState.buttons should be updated before calling this method.
131+ */
132+ - (void )dispatchMouseEvent : (nonnull NSEvent *)event ;
133+
84134/* *
85135 * Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.
86136 */
@@ -447,10 +497,24 @@ - (void)handlePlatformMessage:(const FlutterPlatformMessage*)message {
447497 }
448498}
449499
500+ - (void )dispatchMouseEvent : (nonnull NSEvent *)event {
501+ FlutterPointerPhase phase = _mouseState.buttons == 0
502+ ? (_mouseState.flutter_state_is_down ? kUp : kHover )
503+ : (_mouseState.flutter_state_is_down ? kMove : kDown );
504+ [self dispatchMouseEvent: event phase: phase];
505+ }
506+
450507- (void )dispatchMouseEvent : (NSEvent *)event phase : (FlutterPointerPhase)phase {
508+ // There are edge cases where the system will deliver enter out of order relative to other
509+ // events (e.g., drag out and back in, release, then click; mouseDown: will be called before
510+ // mouseEntered:). Discard those events, since the add will already have been synthesized.
511+ if (_mouseState.flutter_state_is_added && phase == kAdd ) {
512+ return ;
513+ }
514+
451515 // If a pointer added event hasn't been sent, synthesize one using this event for the basic
452516 // information.
453- if (!_mouseCurrentlyAdded && phase != kAdd ) {
517+ if (!_mouseState. flutter_state_is_added && phase != kAdd ) {
454518 // Only the values extracted for use in flutterEvent below matter, the rest are dummy values.
455519 NSEvent * addEvent = [NSEvent enterExitEventWithType: NSEventTypeMouseEntered
456520 location: event.locationInWindow
@@ -468,10 +532,13 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
468532 NSPoint locationInBackingCoordinates = [self .view convertPointToBacking: locationInView];
469533 FlutterPointerEvent flutterEvent = {
470534 .struct_size = sizeof (flutterEvent),
535+ .device_kind = kFlutterPointerDeviceKindMouse ,
471536 .phase = phase,
472537 .x = locationInBackingCoordinates.x ,
473538 .y = -locationInBackingCoordinates.y , // convertPointToBacking makes this negative.
474539 .timestamp = static_cast <size_t >(event.timestamp * NSEC_PER_MSEC),
540+ // If a click triggered a synthesized kAdd, don't pass the buttons in that event.
541+ .buttons = phase == kAdd ? 0 : _mouseState.buttons ,
475542 };
476543
477544 if (event.type == NSEventTypeScrollWheel) {
@@ -491,10 +558,19 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
491558 }
492559 FlutterEngineSendPointerEvent (_engine, &flutterEvent, 1 );
493560
494- if (phase == kAdd ) {
495- _mouseCurrentlyAdded = YES ;
561+ // Update tracking of state as reported to Flutter.
562+ if (phase == kDown ) {
563+ _mouseState.flutter_state_is_down = true ;
564+ } else if (phase == kUp ) {
565+ _mouseState.flutter_state_is_down = false ;
566+ if (_mouseState.has_pending_exit ) {
567+ [self dispatchMouseEvent: event phase: kRemove ];
568+ _mouseState.has_pending_exit = false ;
569+ }
570+ } else if (phase == kAdd ) {
571+ _mouseState.flutter_state_is_added = true ;
496572 } else if (phase == kRemove ) {
497- _mouseCurrentlyAdded = NO ;
573+ _mouseState. Reset () ;
498574 }
499575}
500576
@@ -622,28 +698,62 @@ - (void)keyUp:(NSEvent*)event {
622698 }
623699}
624700
701+ - (void )mouseEntered : (NSEvent *)event {
702+ [self dispatchMouseEvent: event phase: kAdd ];
703+ }
704+
705+ - (void )mouseExited : (NSEvent *)event {
706+ if (_mouseState.buttons != 0 ) {
707+ _mouseState.has_pending_exit = true ;
708+ return ;
709+ }
710+ [self dispatchMouseEvent: event phase: kRemove ];
711+ }
712+
625713- (void )mouseDown : (NSEvent *)event {
626- [self dispatchMouseEvent: event phase: kDown ];
714+ _mouseState.buttons |= kFlutterPointerButtonMousePrimary ;
715+ [self dispatchMouseEvent: event];
627716}
628717
629718- (void )mouseUp : (NSEvent *)event {
630- [self dispatchMouseEvent: event phase: kUp ];
719+ _mouseState.buttons &= ~static_cast <uint64_t >(kFlutterPointerButtonMousePrimary );
720+ [self dispatchMouseEvent: event];
631721}
632722
633723- (void )mouseDragged : (NSEvent *)event {
634- [self dispatchMouseEvent: event phase: kMove ];
724+ [self dispatchMouseEvent: event];
635725}
636726
637- - (void )mouseEntered : (NSEvent *)event {
638- [self dispatchMouseEvent: event phase: kAdd ];
727+ - (void )rightMouseDown : (NSEvent *)event {
728+ _mouseState.buttons |= kFlutterPointerButtonMouseSecondary ;
729+ [self dispatchMouseEvent: event];
639730}
640731
641- - (void )mouseExited : (NSEvent *)event {
642- [self dispatchMouseEvent: event phase: kRemove ];
732+ - (void )rightMouseUp : (NSEvent *)event {
733+ _mouseState.buttons &= ~static_cast <uint64_t >(kFlutterPointerButtonMouseSecondary );
734+ [self dispatchMouseEvent: event];
735+ }
736+
737+ - (void )rightMouseDragged : (NSEvent *)event {
738+ [self dispatchMouseEvent: event];
739+ }
740+
741+ - (void )otherMouseDown : (NSEvent *)event {
742+ _mouseState.buttons |= (1 << event.buttonNumber );
743+ [self dispatchMouseEvent: event];
744+ }
745+
746+ - (void )otherMouseUp : (NSEvent *)event {
747+ _mouseState.buttons &= ~static_cast <uint64_t >(1 << event.buttonNumber );
748+ [self dispatchMouseEvent: event];
749+ }
750+
751+ - (void )otherMouseDragged : (NSEvent *)event {
752+ [self dispatchMouseEvent: event];
643753}
644754
645755- (void )mouseMoved : (NSEvent *)event {
646- [self dispatchMouseEvent: event phase: kHover ];
756+ [self dispatchMouseEvent: event];
647757}
648758
649759- (void )scrollWheel : (NSEvent *)event {
0 commit comments