From 8d6dbd2b0d17ee6c03f6cc9d9bf22f05bbe525c7 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 17 Apr 2024 13:00:34 +0200 Subject: [PATCH] [macOS] FlutterView should not override platform view cursor --- .../macos/framework/Source/FlutterView.mm | 5 + .../macos/framework/Source/FlutterViewTest.mm | 121 ++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterView.mm b/shell/platform/darwin/macos/framework/Source/FlutterView.mm index 29eec7364d457..bafe6984a9e41 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterView.mm @@ -106,6 +106,11 @@ - (void)didUpdateMouseCursor:(NSCursor*)cursor { // - When context menu above FlutterView is closed. Context menu will change current cursor to arrow // and will not restore it back. - (void)cursorUpdate:(NSEvent*)event { + // Make sure to not override cursor when over a platform view. + NSView* hitTestView = [self hitTest:[self convertPoint:event.locationInWindow fromView:nil]]; + if (hitTestView != self) { + return; + } [_lastCursor set]; // It is possible that there is a platform view with NSTrackingArea below flutter content. // This could override the mouse cursor as a result of mouse move event. There is no good way diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm index 65620a53824e4..8e80077906cfe 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm @@ -37,3 +37,124 @@ - (BOOL)viewShouldAcceptFirstResponder:(NSView*)view { viewId:kImplicitViewId]; EXPECT_EQ([view layer:view.layer shouldInheritContentsScale:3.0 fromWindow:view.window], YES); } + +@interface TestFlutterView : FlutterView + +@property(readwrite, nonatomic) NSView* (^onHitTest)(NSPoint point); + +@end + +@implementation TestFlutterView + +@synthesize onHitTest; + +- (NSView*)hitTest:(NSPoint)point { + return self.onHitTest(point); +} + +- (void)reshaped { + // Disable resize synchronization for testing. +} + +@end + +@interface TestCursor : NSCursor +@property(readwrite, nonatomic) BOOL setCalled; +@end + +@implementation TestCursor + +- (void)set { + self.setCalled = YES; +} + +@end + +TEST(FlutterView, CursorUpdateDoesHitTest) { + id device = MTLCreateSystemDefaultDevice(); + id queue = [device newCommandQueue]; + TestFlutterViewDelegate* delegate = [[TestFlutterViewDelegate alloc] init]; + FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init]; + TestFlutterView* view = [[TestFlutterView alloc] initWithMTLDevice:device + commandQueue:queue + delegate:delegate + threadSynchronizer:threadSynchronizer + viewId:kImplicitViewId]; + NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + + TestCursor* cursor = [[TestCursor alloc] init]; + + window.contentView = view; + __weak NSView* weakView = view; + __block BOOL hitTestCalled = NO; + __block NSPoint hitTestCoordinate = NSZeroPoint; + view.onHitTest = ^NSView*(NSPoint point) { + hitTestCalled = YES; + hitTestCoordinate = point; + return weakView; + }; + NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved + location:NSMakePoint(100, 100) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + eventNumber:0 + clickCount:0 + pressure:0]; + [view didUpdateMouseCursor:cursor]; + [view cursorUpdate:mouseEvent]; + + EXPECT_TRUE(hitTestCalled); + // The hit test coordinate should be in the window coordinate system. + EXPECT_TRUE(CGPointEqualToPoint(hitTestCoordinate, CGPointMake(100, 500))); + EXPECT_TRUE(cursor.setCalled); +} + +TEST(FlutterView, CursorUpdateDoesNotOverridePlatformView) { + id device = MTLCreateSystemDefaultDevice(); + id queue = [device newCommandQueue]; + TestFlutterViewDelegate* delegate = [[TestFlutterViewDelegate alloc] init]; + FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init]; + TestFlutterView* view = [[TestFlutterView alloc] initWithMTLDevice:device + commandQueue:queue + delegate:delegate + threadSynchronizer:threadSynchronizer + viewId:kImplicitViewId]; + NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + + TestCursor* cursor = [[TestCursor alloc] init]; + + NSView* platformView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + + window.contentView = view; + __block BOOL hitTestCalled = NO; + __block NSPoint hitTestCoordinate = NSZeroPoint; + view.onHitTest = ^NSView*(NSPoint point) { + hitTestCalled = YES; + hitTestCoordinate = point; + return platformView; + }; + NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved + location:NSMakePoint(100, 100) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + eventNumber:0 + clickCount:0 + pressure:0]; + [view didUpdateMouseCursor:cursor]; + [view cursorUpdate:mouseEvent]; + + EXPECT_TRUE(hitTestCalled); + // The hit test coordinate should be in the window coordinate system. + EXPECT_TRUE(CGPointEqualToPoint(hitTestCoordinate, CGPointMake(100, 500))); + EXPECT_FALSE(cursor.setCalled); +}