Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9d2d58a

Browse files
Add mouse button support to the macOS shell (#9054)
Uses the new embedding API support for device type and buttons to pass appropriate mouse button events, allowing for right click, middle click, etc. Also fixes some edge cases where macOS event delivery violated Flutter requirements by tracking more data about the mouse event stream and adjusting the sent events as necessary.
1 parent b17c0c6 commit 9d2d58a

File tree

1 file changed

+127
-17
lines changed

1 file changed

+127
-17
lines changed

shell/platform/darwin/macos/framework/Source/FLEViewController.mm

Lines changed: 127 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,52 @@
1616

1717
static 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

Comments
 (0)