System mouse cursor: macOS (#18131)
* Untested macOS impl
* Workable mac
* Modernize
* Simplify
* Address comments
* Refactor with FlutterPlugin
* Cached dict
* Update per comments
* format
* Remove device arg
* Format
* Update license
* Update licences
* Doc update
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 1ef5628..7ed3bbe 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -959,6 +959,8 @@
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm
+FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
+FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h
diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn
index dccf1ff..2e5a9cc 100644
--- a/shell/platform/darwin/macos/BUILD.gn
+++ b/shell/platform/darwin/macos/BUILD.gn
@@ -54,6 +54,8 @@
"framework/Source/FlutterEngine_Internal.h",
"framework/Source/FlutterExternalTextureGL.h",
"framework/Source/FlutterExternalTextureGL.mm",
+ "framework/Source/FlutterMouseCursorPlugin.h",
+ "framework/Source/FlutterMouseCursorPlugin.mm",
"framework/Source/FlutterTextInputModel.h",
"framework/Source/FlutterTextInputModel.mm",
"framework/Source/FlutterTextInputPlugin.h",
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
new file mode 100644
index 0000000..73b1d63
--- /dev/null
+++ b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
+#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
+
+/**
+ * A plugin to handle mouse cursor.
+ *
+ * Responsible for bridging the native macOS mouse cursor system with the
+ * Flutter framework mouse cursor classes, via system channels.
+ */
+@interface FlutterMouseCursorPlugin : NSObject <FlutterPlugin>
+
+@end
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
new file mode 100644
index 0000000..6bf1972
--- /dev/null
+++ b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
@@ -0,0 +1,153 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <objc/message.h>
+
+#import "FlutterMouseCursorPlugin.h"
+#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
+
+static NSString* const kMouseCursorChannel = @"flutter/mousecursor";
+
+static NSString* const kActivateSystemCursorMethod = @"activateSystemCursor";
+static NSString* const kKindKey = @"kind";
+
+static NSString* const kKindValueNone = @"none";
+
+/**
+ * Maps a Flutter's constant to a platform's cursor object.
+ *
+ * Returns the arrow cursor for unknown constants, including kSystemShapeNone.
+ */
+static NSCursor* GetCursorForKind(NSString* kind) {
+ // The following mapping must be kept in sync with Flutter framework's
+ // mouse_cursor.dart
+ if ([kind isEqualToString:@"basic"])
+ return [NSCursor arrowCursor];
+ else if ([kind isEqualToString:@"click"])
+ return [NSCursor pointingHandCursor];
+ else if ([kind isEqualToString:@"text"])
+ return [NSCursor IBeamCursor];
+ else if ([kind isEqualToString:@"forbidden"])
+ return [NSCursor operationNotAllowedCursor];
+ else if ([kind isEqualToString:@"grab"])
+ return [NSCursor openHandCursor];
+ else if ([kind isEqualToString:@"grabbing"])
+ return [NSCursor closedHandCursor];
+ else
+ return [NSCursor arrowCursor];
+}
+
+@interface FlutterMouseCursorPlugin ()
+/**
+ * Whether the cursor is currently hidden.
+ */
+@property(nonatomic) BOOL hidden;
+
+/**
+ * Handles the method call that activates a system cursor.
+ *
+ * Returns a FlutterError if the arguments can not be recognized. Otherwise
+ * returns nil.
+ */
+- (FlutterError*)activateSystemCursor:(nonnull NSDictionary*)arguments;
+
+/**
+ * Displays the specified cursor.
+ *
+ * Unhides the cursor before displaying the cursor, and updates
+ * internal states.
+ */
+- (void)displayCursorObject:(nonnull NSCursor*)cursorObject;
+
+/**
+ * Hides the cursor.
+ */
+- (void)hide;
+
+/**
+ * Handles all method calls from Flutter.
+ */
+- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
+
+@end
+
+@implementation FlutterMouseCursorPlugin
+
+#pragma mark - Private
+
+NSMutableDictionary* cachedSystemCursors;
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ cachedSystemCursors = [NSMutableDictionary dictionary];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (_hidden) {
+ [NSCursor unhide];
+ }
+}
+
+- (FlutterError*)activateSystemCursor:(nonnull NSDictionary*)arguments {
+ NSString* kindArg = arguments[kKindKey];
+ if (!kindArg) {
+ return [FlutterError errorWithCode:@"error"
+ message:@"Missing argument"
+ details:@"Missing argument while trying to activate system cursor"];
+ }
+ if ([kindArg isEqualToString:kKindValueNone]) {
+ [self hide];
+ return nil;
+ }
+ NSCursor* cursorObject = [FlutterMouseCursorPlugin cursorFromKind:kindArg];
+ [self displayCursorObject:cursorObject];
+ return nil;
+}
+
+- (void)displayCursorObject:(nonnull NSCursor*)cursorObject {
+ [cursorObject set];
+ if (_hidden) {
+ [NSCursor unhide];
+ }
+ _hidden = NO;
+}
+
+- (void)hide {
+ if (!_hidden) {
+ [NSCursor hide];
+ }
+ _hidden = YES;
+}
+
++ (NSCursor*)cursorFromKind:(NSString*)kind {
+ NSCursor* cachedValue = [cachedSystemCursors objectForKey:kind];
+ if (!cachedValue) {
+ cachedValue = GetCursorForKind(kind);
+ [cachedSystemCursors setValue:cachedValue forKey:kind];
+ }
+ return cachedValue;
+}
+
+#pragma mark - FlutterPlugin implementation
+
++ (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
+ FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:kMouseCursorChannel
+ binaryMessenger:registrar.messenger];
+ FlutterMouseCursorPlugin* instance = [[FlutterMouseCursorPlugin alloc] init];
+ [registrar addMethodCallDelegate:instance channel:channel];
+}
+
+- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
+ NSString* method = call.method;
+ if ([method isEqualToString:kActivateSystemCursorMethod]) {
+ result([self activateSystemCursor:call.arguments]);
+ } else {
+ result(FlutterMethodNotImplemented);
+ }
+}
+
+@end
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
index 4813ab8..31708f2 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
@@ -9,6 +9,7 @@
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
+#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
#import "flutter/shell/platform/embedder/embedder.h"
@@ -350,6 +351,7 @@
}
- (void)addInternalPlugins {
+ [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
_textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self];
_keyEventChannel =
[FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent"