diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a9daedd5960ed..61eb38d14c524 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -957,6 +957,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngin 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 dccf1ff49237f..2e5a9cc6e7423 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -54,6 +54,8 @@ source_set("flutter_framework_source") { "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 0000000000000..73b1d63e87d92 --- /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 + +#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 + +@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 0000000000000..6bf19720c7194 --- /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 + +#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)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 4813ab8fe2279..31708f233ce37 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)configureTrackingArea { } - (void)addInternalPlugins { + [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self]; _keyEventChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent"