mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
Initial import of FDE macOS framework (#7642)
Merges the current flutter-desktop-embedding macOS framework into the engine. Notable changes: - All channel/codec related code is eliminated in favor of using the existing iOS implementations. - All .m files renamed to .mm for consistency with the iOS code. - Some minor code changes to fix new warnings in Objective-C++ mode. - License headers, basic format (e.g., clang-format changes) updated to use repo style. - Xcode project is not included; instead adds GN build rules to create an integrated framework that combines what was the FDE library with what is present in FlutterEmbedder.framework. Other changes are left as follow-ups, including: - Moving shared code out of ios/ into common/. - Potentially improving sharing between iOS and macOS BUILD.gn. - Class renaming; the FLE prefix will be eliminated, but that API surface isn't stable yet, so that can be changed later.
This commit is contained in:
@@ -557,6 +557,22 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEOpenGLContextHandling.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEPlugin.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEPluginRegistrar.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/Flutter.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Info.plist
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEView.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEViewController.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap
|
||||
FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist
|
||||
FILE: ../../../flutter/shell/platform/embedder/assets/embedder.modulemap
|
||||
FILE: ../../../flutter/shell/platform/embedder/embedder.cc
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
|
||||
assert(is_mac || is_ios)
|
||||
|
||||
import("framework_shared.gni")
|
||||
|
||||
group("darwin") {
|
||||
if (is_ios) {
|
||||
deps = [
|
||||
"ios:flutter_framework",
|
||||
]
|
||||
}
|
||||
if (is_mac) {
|
||||
deps = [
|
||||
"macos:flutter_framework",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("flutter_channels") {
|
||||
@@ -40,6 +47,22 @@ source_set("flutter_channels") {
|
||||
public_configs = [ "$flutter_root:config" ]
|
||||
}
|
||||
|
||||
# Framework code shared between iOS and macOS.
|
||||
source_set("framework_shared") {
|
||||
set_sources_assignment_filter([])
|
||||
sources = [
|
||||
"ios/framework/Source/FlutterChannels.mm",
|
||||
"ios/framework/Source/FlutterCodecs.mm",
|
||||
"ios/framework/Source/FlutterStandardCodec.mm",
|
||||
"ios/framework/Source/FlutterStandardCodec_Internal.h",
|
||||
]
|
||||
|
||||
public = framework_shared_headers
|
||||
set_sources_assignment_filter(sources_assignment_filter)
|
||||
|
||||
defines = [ "FLUTTER_FRAMEWORK" ]
|
||||
}
|
||||
|
||||
executable("flutter_channels_unittests") {
|
||||
testonly = true
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
framework_shared_headers = get_path_info(
|
||||
[
|
||||
# TODO: Move these files, and their implementations, to a shared
|
||||
# location.
|
||||
"ios/framework/Headers/FlutterMacros.h",
|
||||
"ios/framework/Headers/FlutterBinaryMessenger.h",
|
||||
"ios/framework/Headers/FlutterChannels.h",
|
||||
"ios/framework/Headers/FlutterCodecs.h",
|
||||
],
|
||||
"abspath")
|
||||
@@ -61,7 +61,10 @@ FLUTTER_EXPORT
|
||||
*/
|
||||
- (void)sendOnChannel:(NSString*)channel
|
||||
message:(NSData* _Nullable)message
|
||||
binaryReply:(FlutterBinaryReply _Nullable)callback;
|
||||
binaryReply:(FlutterBinaryReply _Nullable)callback
|
||||
// TODO: Add macOS support for replies once
|
||||
// https://github.com/flutter/flutter/issues/18852 is fixed.
|
||||
API_UNAVAILABLE(macos);
|
||||
|
||||
/**
|
||||
* Registers a message handler for incoming binary messages from the Flutter side
|
||||
|
||||
@@ -103,7 +103,11 @@ FLUTTER_EXPORT
|
||||
* @param message The message. Must be supported by the codec of this channel.
|
||||
* @param callback A callback to be invoked with the message reply from Flutter.
|
||||
*/
|
||||
- (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback;
|
||||
- (void)sendMessage:(id _Nullable)message
|
||||
reply:(FlutterReply _Nullable)callback
|
||||
// TODO: Add macOS support for replies once
|
||||
// https://github.com/flutter/flutter/issues/18852 is fixed.
|
||||
API_UNAVAILABLE(macos);
|
||||
|
||||
/**
|
||||
* Registers a message handler with this channel.
|
||||
@@ -234,7 +238,10 @@ FLUTTER_EXPORT
|
||||
*/
|
||||
- (void)invokeMethod:(NSString*)method
|
||||
arguments:(id _Nullable)arguments
|
||||
result:(FlutterResult _Nullable)callback;
|
||||
result:(FlutterResult _Nullable)callback
|
||||
// TODO: Add macOS support for replies once
|
||||
// https://github.com/flutter/flutter/issues/18852 is fixed.
|
||||
API_UNAVAILABLE(macos);
|
||||
|
||||
/**
|
||||
* Registers a handler for method calls from the Flutter side.
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# 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.
|
||||
|
||||
assert(is_mac)
|
||||
|
||||
import("//build/config/mac/mac_sdk.gni")
|
||||
import("$flutter_root/common/config.gni")
|
||||
import("$flutter_root/shell/platform/darwin/framework_shared.gni")
|
||||
|
||||
_flutter_framework_name = "Flutter"
|
||||
_flutter_framework_out_dir_subpath = "macos/$_flutter_framework_name.framework"
|
||||
_flutter_framework_dir = "$root_out_dir/$_flutter_framework_out_dir_subpath"
|
||||
|
||||
# The headers that will be copied to the Flutter.framework and be accessed
|
||||
# from outside the Flutter engine source root.
|
||||
_flutter_framework_headers = [
|
||||
"framework/Headers/Flutter.h",
|
||||
"framework/Headers/FLEOpenGLContextHandling.h",
|
||||
"framework/Headers/FLEPlugin.h",
|
||||
"framework/Headers/FLEPluginRegistrar.h",
|
||||
"framework/Headers/FLEReshapeListener.h",
|
||||
"framework/Headers/FLEView.h",
|
||||
"framework/Headers/FLEViewController.h",
|
||||
]
|
||||
|
||||
_flutter_framework_headers_copy_dir =
|
||||
"$_flutter_framework_dir/Versions/A/Headers"
|
||||
|
||||
shared_library("create_flutter_framework_dylib") {
|
||||
visibility = [ ":*" ]
|
||||
|
||||
output_name = "$_flutter_framework_name"
|
||||
|
||||
sources = [
|
||||
"framework/Source/FLETextInputModel.h",
|
||||
"framework/Source/FLETextInputModel.mm",
|
||||
"framework/Source/FLETextInputPlugin.h",
|
||||
"framework/Source/FLETextInputPlugin.mm",
|
||||
"framework/Source/FLEView.mm",
|
||||
"framework/Source/FLEViewController.mm",
|
||||
"framework/Source/FLEViewController_Internal.h",
|
||||
]
|
||||
|
||||
sources += _flutter_framework_headers
|
||||
|
||||
deps = [
|
||||
"$flutter_root/shell/platform/darwin:framework_shared",
|
||||
"$flutter_root/shell/platform/embedder:embedder",
|
||||
]
|
||||
|
||||
public_configs = [ "$flutter_root:config" ]
|
||||
|
||||
defines = [ "FLUTTER_FRAMEWORK" ]
|
||||
|
||||
cflags_objcc = [ "-fobjc-arc" ]
|
||||
|
||||
libs = [ "Cocoa.framework" ]
|
||||
}
|
||||
|
||||
copy("copy_framework_dylib") {
|
||||
visibility = [ ":*" ]
|
||||
|
||||
sources = [
|
||||
"$root_out_dir/lib$_flutter_framework_name.dylib",
|
||||
]
|
||||
outputs = [
|
||||
"$_flutter_framework_dir/Versions/A/$_flutter_framework_name",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":create_flutter_framework_dylib",
|
||||
]
|
||||
}
|
||||
|
||||
action("copy_dylib_and_update_framework_install_name") {
|
||||
visibility = [ ":*" ]
|
||||
stamp_file = "$root_out_dir/flutter_install_name_stamp"
|
||||
script = "$flutter_root/sky/tools/change_install_name.py"
|
||||
|
||||
inputs = [
|
||||
"$_flutter_framework_dir/Versions/A/$_flutter_framework_name",
|
||||
]
|
||||
outputs = [
|
||||
stamp_file,
|
||||
]
|
||||
|
||||
args = [
|
||||
"--dylib",
|
||||
rebase_path("$_flutter_framework_dir/Versions/A/$_flutter_framework_name"),
|
||||
"--install_name",
|
||||
"@rpath/$_flutter_framework_name.framework/Versions/A/$_flutter_framework_name",
|
||||
"--stamp",
|
||||
rebase_path(stamp_file),
|
||||
]
|
||||
|
||||
deps = [
|
||||
":copy_framework_dylib",
|
||||
]
|
||||
}
|
||||
|
||||
copy("copy_framework_info_plist") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"framework/Info.plist",
|
||||
]
|
||||
outputs = [
|
||||
"$_flutter_framework_dir/Versions/A/Resources/Info.plist",
|
||||
]
|
||||
}
|
||||
|
||||
copy("copy_framework_module_map") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"framework/module.modulemap",
|
||||
]
|
||||
outputs = [
|
||||
"$_flutter_framework_dir/Versions/A/Modules/module.modulemap",
|
||||
]
|
||||
}
|
||||
|
||||
action("copy_framework_headers") {
|
||||
script = "$flutter_root/sky/tools/install_framework_headers.py"
|
||||
visibility = [ ":*" ]
|
||||
set_sources_assignment_filter([])
|
||||
sources = get_path_info(_flutter_framework_headers, "abspath") +
|
||||
framework_shared_headers
|
||||
outputs = []
|
||||
foreach(header, sources) {
|
||||
header_basename = get_path_info(header, "file")
|
||||
outputs += [ "$_flutter_framework_headers_copy_dir/$header_basename" ]
|
||||
}
|
||||
args = [
|
||||
"--location",
|
||||
rebase_path("$_flutter_framework_headers_copy_dir"),
|
||||
"--headers",
|
||||
] + rebase_path(sources, "", "//")
|
||||
set_sources_assignment_filter(sources_assignment_filter)
|
||||
}
|
||||
|
||||
copy("copy_framework_icu") {
|
||||
visibility = [ ":*" ]
|
||||
set_sources_assignment_filter([])
|
||||
sources = [
|
||||
"//third_party/icu/flutter/icudtl.dat",
|
||||
]
|
||||
set_sources_assignment_filter(sources_assignment_filter)
|
||||
outputs = [
|
||||
"$_flutter_framework_dir/Versions/A/Resources/{{source_file_part}}",
|
||||
]
|
||||
}
|
||||
|
||||
copy("copy_license") {
|
||||
visibility = [ ":*" ]
|
||||
sources = [
|
||||
"//LICENSE",
|
||||
]
|
||||
outputs = [
|
||||
"$root_out_dir/LICENSE",
|
||||
]
|
||||
}
|
||||
|
||||
action("_generate_symlinks") {
|
||||
visibility = [ ":*" ]
|
||||
script = "//build/config/mac/package_framework.py"
|
||||
outputs = [
|
||||
"$root_build_dir/$_flutter_framework_name.stamp",
|
||||
]
|
||||
args = [
|
||||
"--framework",
|
||||
"$_flutter_framework_out_dir_subpath",
|
||||
"--version",
|
||||
"A",
|
||||
"--contents",
|
||||
"$_flutter_framework_name",
|
||||
"Resources",
|
||||
"Headers",
|
||||
"Modules",
|
||||
"--stamp",
|
||||
"$_flutter_framework_name.stamp",
|
||||
]
|
||||
deps = [
|
||||
":copy_dylib_and_update_framework_install_name",
|
||||
":copy_framework_headers",
|
||||
":copy_framework_icu",
|
||||
":copy_framework_info_plist",
|
||||
":copy_framework_module_map",
|
||||
":copy_license",
|
||||
]
|
||||
}
|
||||
|
||||
group("flutter_framework") {
|
||||
deps = [
|
||||
":_generate_symlinks",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Protocol for views owned by FLEViewController to handle context changes, specifically relating to
|
||||
* OpenGL context changes.
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@protocol FLEOpenGLContextHandling
|
||||
|
||||
/**
|
||||
* Sets the receiver as the current context object.
|
||||
*/
|
||||
- (void)makeCurrentContext;
|
||||
|
||||
/**
|
||||
* Called when the display is updated. In an NSOpenGLView this is best handled via a flushBuffer
|
||||
* call.
|
||||
*/
|
||||
- (void)onPresent;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterChannels.h"
|
||||
#import "FlutterCodecs.h"
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
@protocol FLEPluginRegistrar;
|
||||
|
||||
/**
|
||||
* Implemented by the platform side of a Flutter plugin.
|
||||
*
|
||||
* Defines a set of optional callback methods and a method to set up the plugin
|
||||
* and register it to be called by other application components.
|
||||
*
|
||||
* Currently FLEPlugin has very limited functionality, but is expected to expand over time to
|
||||
* more closely match the functionality of FlutterPlugin.
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@protocol FLEPlugin <NSObject>
|
||||
|
||||
/**
|
||||
* Creates an instance of the plugin to register with |registrar| using the desired
|
||||
* FLEPluginRegistrar methods.
|
||||
*/
|
||||
+ (void)registerWithRegistrar:(nonnull id<FLEPluginRegistrar>)registrar;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Called when a message is sent from Flutter on a channel that a plugin instance has subscribed
|
||||
* to via -[FLEPluginRegistrar addMethodCallDelegate:channel:].
|
||||
*
|
||||
* The |result| callback must be called exactly once, with one of:
|
||||
* - FlutterMethodNotImplemented, if the method call is unknown.
|
||||
* - A FlutterError, if the method call was understood but there was a
|
||||
* problem handling it.
|
||||
* - Any other value (including nil) to indicate success. The value will
|
||||
* be returned to the Flutter caller, and must be serializable to JSON.
|
||||
*/
|
||||
- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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 "FLEPlugin.h"
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterBinaryMessenger.h"
|
||||
#import "FlutterChannels.h"
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The protocol for an object managing registration for a plugin. It provides access to application
|
||||
* context, as as allowing registering for callbacks for handling various conditions.
|
||||
*
|
||||
* Currently FLEPluginRegistrar has very limited functionality, but is expected to expand over time
|
||||
* to more closely match the functionality of FlutterPluginRegistrar.
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@protocol FLEPluginRegistrar <NSObject>
|
||||
|
||||
/**
|
||||
* The binary messenger used for creating channels to communicate with the Flutter engine.
|
||||
*/
|
||||
@property(nonnull, readonly) id<FlutterBinaryMessenger> messenger;
|
||||
|
||||
/**
|
||||
* The view displaying Flutter content.
|
||||
*
|
||||
* WARNING: If/when multiple Flutter views within the same application are supported (#98), this
|
||||
* API will change.
|
||||
*/
|
||||
@property(nullable, readonly) NSView* view;
|
||||
|
||||
/**
|
||||
* Registers |delegate| to receive handleMethodCall:result: callbacks for the given |channel|.
|
||||
*/
|
||||
- (void)addMethodCallDelegate:(nonnull id<FLEPlugin>)delegate
|
||||
channel:(nonnull FlutterMethodChannel*)channel;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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>
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Protocol for listening to reshape events on this FlutterView.
|
||||
* Used to notify the underlying Flutter engine of the new screen dimensions.
|
||||
* Reflected from [NSOpenGLView.reshape].
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@protocol FLEReshapeListener
|
||||
|
||||
- (void)viewDidReshape:(nonnull NSOpenGLView*)view;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 "FLEOpenGLContextHandling.h"
|
||||
#import "FLEReshapeListener.h"
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* View capable of acting as a rendering target and input source for the Flutter
|
||||
* engine.
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@interface FLEView : NSOpenGLView <FLEOpenGLContextHandling>
|
||||
|
||||
/**
|
||||
* Listener for reshape events. See protocol description.
|
||||
*/
|
||||
@property(nonatomic, weak, nullable) IBOutlet id<FLEReshapeListener> reshapeListener;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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 "FLEOpenGLContextHandling.h"
|
||||
#import "FLEPluginRegistrar.h"
|
||||
#import "FLEReshapeListener.h"
|
||||
|
||||
#if defined(FLUTTER_FRAMEWORK)
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterMacros.h"
|
||||
#else
|
||||
#import "FlutterBinaryMessenger.h"
|
||||
#import "FlutterMacros.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Controls embedder plugins and communication with the underlying Flutter engine, managing a view
|
||||
* intended to handle key inputs and drawing protocols (see |view|).
|
||||
*
|
||||
* Can be launched headless (no managed view), at which point a Dart executable will be run on the
|
||||
* Flutter engine in non-interactive mode, or with a drawable Flutter canvas.
|
||||
*/
|
||||
FLUTTER_EXPORT
|
||||
@interface FLEViewController
|
||||
: NSViewController <FlutterBinaryMessenger, FLEPluginRegistrar, FLEReshapeListener>
|
||||
|
||||
/**
|
||||
* The view this controller manages when launched in interactive mode (headless set to false). Must
|
||||
* be capable of handling text input events, and the OpenGL context handling protocols.
|
||||
*/
|
||||
@property(nullable) NSView<FLEOpenGLContextHandling>* view;
|
||||
|
||||
/**
|
||||
* Launches the Flutter engine with the provided configuration.
|
||||
*
|
||||
* @param assets The path to the flutter_assets folder for the Flutter application to be run.
|
||||
* @param arguments Arguments to pass to the Flutter engine. See
|
||||
* https://github.com/flutter/engine/blob/master/shell/common/switches.h
|
||||
* for details. Not all arguments will apply to embedding mode.
|
||||
* Note: This API layer will likely abstract arguments in the future, instead of
|
||||
* providing a direct passthrough.
|
||||
* @return YES if the engine launched successfully.
|
||||
*/
|
||||
- (BOOL)launchEngineWithAssetsPath:(nonnull NSURL*)assets
|
||||
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
|
||||
|
||||
/**
|
||||
* Launches the Flutter engine in headless mode with the provided configuration. In headless mode,
|
||||
* this controller's view should not be displayed.
|
||||
*
|
||||
* See launcheEngineWithAssetsPath:commandLineArguments: for details.
|
||||
*/
|
||||
- (BOOL)launchHeadlessEngineWithAssetsPath:(nonnull NSURL*)assets
|
||||
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
|
||||
|
||||
/**
|
||||
* Returns the FLEPluginRegistrar that should be used to register the plugin with the given name.
|
||||
*/
|
||||
- (nonnull id<FLEPluginRegistrar>)registrarForPlugin:(nonnull NSString*)pluginName;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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 "FLEOpenGLContextHandling.h"
|
||||
#import "FLEPlugin.h"
|
||||
#import "FLEPluginRegistrar.h"
|
||||
#import "FLEReshapeListener.h"
|
||||
#import "FLEView.h"
|
||||
#import "FLEViewController.h"
|
||||
#import "FlutterBinaryMessenger.h"
|
||||
#import "FlutterChannels.h"
|
||||
#import "FlutterCodecs.h"
|
||||
#import "FlutterMacros.h"
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Flutter</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Flutter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* The affinity of the current cursor position. If the cursor is at a position representing
|
||||
* a line break, the cursor may be drawn either at the end of the current line (upstream)
|
||||
* or at the beginning of the next (downstream).
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, FLETextAffinity) { FLETextAffinityUpstream, FLETextAffinityDownstream };
|
||||
|
||||
/**
|
||||
* Data model representing text input state during an editing session.
|
||||
*/
|
||||
@interface FLETextInputModel : NSObject
|
||||
|
||||
/**
|
||||
* The full text being edited.
|
||||
*/
|
||||
@property(nonnull, copy) NSMutableString* text;
|
||||
/**
|
||||
* The range of text currently selected. This may have length zero to represent a single
|
||||
* cursor position.
|
||||
*/
|
||||
@property NSRange selectedRange;
|
||||
/**
|
||||
* The affinity for the current cursor position.
|
||||
*/
|
||||
@property FLETextAffinity textAffinity;
|
||||
/**
|
||||
* The range of text that is marked for edit, i.e. under the effects of a multi-keystroke input
|
||||
* combination.
|
||||
*/
|
||||
@property NSRange markedRange;
|
||||
|
||||
/**
|
||||
* Representation of the model's data as a state dictionary suitable for interchange with the
|
||||
* Flutter Dart layer.
|
||||
*/
|
||||
@property(nonnull) NSDictionary* state;
|
||||
|
||||
/**
|
||||
* ID of the text input client.
|
||||
*/
|
||||
@property(nonatomic, readonly, nonnull) NSNumber* clientID;
|
||||
|
||||
/**
|
||||
* Keyboard type of the client. See available options:
|
||||
* https://docs.flutter.io/flutter/services/TextInputType-class.html
|
||||
*/
|
||||
@property(nonatomic, readonly, nonnull) NSString* inputType;
|
||||
|
||||
/**
|
||||
* An action requested by the user on the input client. See available options:
|
||||
* https://docs.flutter.io/flutter/services/TextInputAction-class.html
|
||||
*/
|
||||
@property(nonatomic, readonly, nonnull) NSString* inputAction;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Initializes a text input model with a [clientId] and [config] arguments. [config] arguments
|
||||
* provide information on the text input connection.
|
||||
*/
|
||||
- (nullable instancetype)initWithClientID:(nonnull NSNumber*)clientID
|
||||
configuration:(nonnull NSDictionary*)config;
|
||||
@end
|
||||
@@ -0,0 +1,123 @@
|
||||
// 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 "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h"
|
||||
|
||||
static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream";
|
||||
static NSString* const kTextAffinityUpstream = @"TextAffinity.upstream";
|
||||
|
||||
static NSString* const kTextInputAction = @"inputAction";
|
||||
static NSString* const kTextInputType = @"inputType";
|
||||
static NSString* const kTextInputTypeName = @"name";
|
||||
|
||||
static NSString* const kSelectionBaseKey = @"selectionBase";
|
||||
static NSString* const kSelectionExtentKey = @"selectionExtent";
|
||||
static NSString* const kSelectionAffinityKey = @"selectionAffinity";
|
||||
static NSString* const kSelectionIsDirectionalKey = @"selectionIsDirectional";
|
||||
static NSString* const kComposingBaseKey = @"composingBase";
|
||||
static NSString* const kComposingExtentKey = @"composingExtent";
|
||||
static NSString* const kTextKey = @"text";
|
||||
|
||||
/**
|
||||
* These three static methods are necessary because Cocoa and Flutter have different idioms for
|
||||
* signalling an empty range: Flutter uses {-1, -1} while Cocoa uses {NSNotFound, 0}. Also,
|
||||
* despite the name, the "extent" fields are actually end indices, not lengths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updates a range given base and extent fields.
|
||||
*/
|
||||
static NSRange UpdateRangeFromBaseExtent(NSNumber* base, NSNumber* extent, NSRange range) {
|
||||
if (base == nil || extent == nil) {
|
||||
return range;
|
||||
}
|
||||
if (base.intValue == -1 && extent.intValue == -1) {
|
||||
range.location = NSNotFound;
|
||||
range.length = 0;
|
||||
} else {
|
||||
range.location = [base unsignedLongValue];
|
||||
range.length = [extent unsignedLongValue] - range.location;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate base field for a given range.
|
||||
*/
|
||||
static long GetBaseForRange(NSRange range) {
|
||||
if (range.location == NSNotFound) {
|
||||
return -1;
|
||||
}
|
||||
return range.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate extent field for a given range.
|
||||
*/
|
||||
static long GetExtentForRange(NSRange range) {
|
||||
if (range.location == NSNotFound) {
|
||||
return -1;
|
||||
}
|
||||
return range.location + range.length;
|
||||
}
|
||||
|
||||
@implementation FLETextInputModel
|
||||
|
||||
- (instancetype)initWithClientID:(NSNumber*)clientID configuration:(NSDictionary*)config {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_clientID = clientID;
|
||||
_inputAction = config[kTextInputAction];
|
||||
// There's more information that can be used from this dictionary.
|
||||
// Add more as needed.
|
||||
NSDictionary* inputTypeInfo = config[kTextInputType];
|
||||
_inputType = inputTypeInfo[kTextInputTypeName];
|
||||
if (!_clientID || !_inputAction || !_inputType) {
|
||||
NSLog(@"Missing arguments for %@ init.", [self class]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
_text = [[NSMutableString alloc] init];
|
||||
_selectedRange = NSMakeRange(NSNotFound, 0);
|
||||
_markedRange = NSMakeRange(NSNotFound, 0);
|
||||
_textAffinity = FLETextAffinityUpstream;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary*)state {
|
||||
NSString* const textAffinity =
|
||||
(_textAffinity == FLETextAffinityUpstream) ? kTextAffinityUpstream : kTextAffinityDownstream;
|
||||
NSDictionary* state = @{
|
||||
kSelectionBaseKey : @(GetBaseForRange(_selectedRange)),
|
||||
kSelectionExtentKey : @(GetExtentForRange(_selectedRange)),
|
||||
kSelectionAffinityKey : textAffinity,
|
||||
kSelectionIsDirectionalKey : @NO,
|
||||
kComposingBaseKey : @(GetBaseForRange(_markedRange)),
|
||||
kComposingExtentKey : @(GetExtentForRange(_markedRange)),
|
||||
kTextKey : _text
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
- (void)setState:(NSDictionary*)state {
|
||||
if (state == nil)
|
||||
return;
|
||||
|
||||
_selectedRange = UpdateRangeFromBaseExtent(state[kSelectionBaseKey], state[kSelectionExtentKey],
|
||||
_selectedRange);
|
||||
NSString* selectionAffinity = state[kSelectionAffinityKey];
|
||||
if (selectionAffinity != nil) {
|
||||
_textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]
|
||||
? FLETextAffinityUpstream
|
||||
: FLETextAffinityDownstream;
|
||||
}
|
||||
_markedRange =
|
||||
UpdateRangeFromBaseExtent(state[kComposingBaseKey], state[kComposingExtentKey], _markedRange);
|
||||
NSString* text = state[kTextKey];
|
||||
if (text != nil)
|
||||
[_text setString:text];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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/ios/framework/Headers/FlutterBinaryMessenger.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h"
|
||||
|
||||
/**
|
||||
* A plugin to handle text input.
|
||||
*
|
||||
* Responsible for bridging the native macOS text input system with the Flutter framework text
|
||||
* editing classes, via system channels.
|
||||
*
|
||||
* This is not an FLEPlugin since it needs access to FLEViewController internals, so needs to be
|
||||
* managed differently.
|
||||
*/
|
||||
@interface FLETextInputPlugin : NSResponder
|
||||
|
||||
/**
|
||||
* Initializes a text input plugin that coordinates key event handling with |viewController|.
|
||||
*/
|
||||
- (instancetype)initWithViewController:(FLEViewController*)viewController;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,309 @@
|
||||
// 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 "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputModel.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h"
|
||||
|
||||
static NSString* const kTextInputChannel = @"flutter/textinput";
|
||||
|
||||
// See https://docs.flutter.io/flutter/services/SystemChannels/textInput-constant.html
|
||||
static NSString* const kSetClientMethod = @"TextInput.setClient";
|
||||
static NSString* const kShowMethod = @"TextInput.show";
|
||||
static NSString* const kHideMethod = @"TextInput.hide";
|
||||
static NSString* const kClearClientMethod = @"TextInput.clearClient";
|
||||
static NSString* const kSetEditingStateMethod = @"TextInput.setEditingState";
|
||||
static NSString* const kUpdateEditStateResponseMethod = @"TextInputClient.updateEditingState";
|
||||
static NSString* const kPerformAction = @"TextInputClient.performAction";
|
||||
static NSString* const kMultilineInputType = @"TextInputType.multiline";
|
||||
|
||||
/**
|
||||
* Private properties of FlutterTextInputPlugin.
|
||||
*/
|
||||
@interface FLETextInputPlugin () <NSTextInputClient>
|
||||
|
||||
/**
|
||||
* A text input context, representing a connection to the Cocoa text input system.
|
||||
*/
|
||||
@property(nonatomic) NSTextInputContext* textInputContext;
|
||||
|
||||
/**
|
||||
* A dictionary of text input models, one per client connection, keyed
|
||||
* by the client connection ID.
|
||||
*/
|
||||
@property(nonatomic) NSMutableDictionary<NSNumber*, FLETextInputModel*>* textInputModels;
|
||||
|
||||
/**
|
||||
* The currently active client connection ID.
|
||||
*/
|
||||
@property(nonatomic, nullable) NSNumber* activeClientID;
|
||||
|
||||
/**
|
||||
* The currently active text input model.
|
||||
*/
|
||||
@property(nonatomic, readonly, nullable) FLETextInputModel* activeModel;
|
||||
|
||||
/**
|
||||
* The channel used to communicate with Flutter.
|
||||
*/
|
||||
@property(nonatomic) FlutterMethodChannel* channel;
|
||||
|
||||
/**
|
||||
* The FLEViewController to manage input for.
|
||||
*/
|
||||
@property(nonatomic, weak) FLEViewController* flutterViewController;
|
||||
|
||||
/**
|
||||
* Handles a Flutter system message on the text input channel.
|
||||
*/
|
||||
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLETextInputPlugin
|
||||
|
||||
- (instancetype)initWithViewController:(FLEViewController*)viewController {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_flutterViewController = viewController;
|
||||
_channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel
|
||||
binaryMessenger:viewController
|
||||
codec:[FlutterJSONMethodCodec sharedInstance]];
|
||||
__weak FLETextInputPlugin* weakSelf = self;
|
||||
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
||||
[weakSelf handleMethodCall:call result:result];
|
||||
}];
|
||||
_textInputModels = [[NSMutableDictionary alloc] init];
|
||||
_textInputContext = [[NSTextInputContext alloc] initWithClient:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (FLETextInputModel*)activeModel {
|
||||
return (_activeClientID == nil) ? nil : _textInputModels[_activeClientID];
|
||||
}
|
||||
|
||||
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
BOOL handled = YES;
|
||||
NSString* method = call.method;
|
||||
if ([method isEqualToString:kSetClientMethod]) {
|
||||
if (!call.arguments[0] || !call.arguments[1]) {
|
||||
result([FlutterError
|
||||
errorWithCode:@"error"
|
||||
message:@"Missing arguments"
|
||||
details:@"Missing arguments while trying to set a text input client"]);
|
||||
return;
|
||||
}
|
||||
NSNumber* clientID = call.arguments[0];
|
||||
if (clientID != nil &&
|
||||
(_activeClientID == nil || ![_activeClientID isEqualToNumber:clientID])) {
|
||||
_activeClientID = clientID;
|
||||
// TODO: Do we need to preserve state across setClient calls?
|
||||
FLETextInputModel* inputModel =
|
||||
[[FLETextInputModel alloc] initWithClientID:clientID configuration:call.arguments[1]];
|
||||
if (!inputModel) {
|
||||
result([FlutterError errorWithCode:@"error"
|
||||
message:@"Failed to create an input model"
|
||||
details:@"Configuration arguments might be missing"]);
|
||||
return;
|
||||
}
|
||||
_textInputModels[_activeClientID] = inputModel;
|
||||
}
|
||||
} else if ([method isEqualToString:kShowMethod]) {
|
||||
[self.flutterViewController addKeyResponder:self];
|
||||
[_textInputContext activate];
|
||||
} else if ([method isEqualToString:kHideMethod]) {
|
||||
[self.flutterViewController removeKeyResponder:self];
|
||||
[_textInputContext deactivate];
|
||||
} else if ([method isEqualToString:kClearClientMethod]) {
|
||||
_activeClientID = nil;
|
||||
} else if ([method isEqualToString:kSetEditingStateMethod]) {
|
||||
NSDictionary* state = call.arguments;
|
||||
self.activeModel.state = state;
|
||||
} else {
|
||||
handled = NO;
|
||||
NSLog(@"Unhandled text input method '%@'", method);
|
||||
}
|
||||
result(handled ? nil : FlutterMethodNotImplemented);
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the Flutter framework of changes to the text input model's state.
|
||||
*/
|
||||
- (void)updateEditState {
|
||||
if (self.activeModel == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_channel invokeMethod:kUpdateEditStateResponseMethod
|
||||
arguments:@[ _activeClientID, _textInputModels[_activeClientID].state ]];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark NSResponder
|
||||
|
||||
/**
|
||||
* Note, the Apple docs suggest that clients should override essentially all the
|
||||
* mouse and keyboard event-handling methods of NSResponder. However, experimentation
|
||||
* indicates that only key events are processed by the native layer; Flutter processes
|
||||
* mouse events. Additionally, processing both keyUp and keyDown results in duplicate
|
||||
* processing of the same keys. So for now, limit processing to just keyDown.
|
||||
*/
|
||||
- (void)keyDown:(NSEvent*)event {
|
||||
[_textInputContext handleEvent:event];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark NSStandardKeyBindingMethods
|
||||
|
||||
/**
|
||||
* Note, experimentation indicates that moveRight and moveLeft are called rather
|
||||
* than the supposedly more RTL-friendly moveForward and moveBackward.
|
||||
*/
|
||||
- (void)moveLeft:(nullable id)sender {
|
||||
NSRange selection = self.activeModel.selectedRange;
|
||||
if (selection.length == 0) {
|
||||
if (selection.location > 0) {
|
||||
// Move to previous location
|
||||
self.activeModel.selectedRange = NSMakeRange(selection.location - 1, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
} else {
|
||||
// Collapse current selection
|
||||
self.activeModel.selectedRange = NSMakeRange(selection.location, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRight:(nullable id)sender {
|
||||
NSRange selection = self.activeModel.selectedRange;
|
||||
if (selection.length == 0) {
|
||||
if (selection.location < self.activeModel.text.length) {
|
||||
// Move to next location
|
||||
self.activeModel.selectedRange = NSMakeRange(selection.location + 1, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
} else {
|
||||
// Collapse current selection
|
||||
self.activeModel.selectedRange = NSMakeRange(selection.location + selection.length, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteBackward:(id)sender {
|
||||
NSRange selection = self.activeModel.selectedRange;
|
||||
if (selection.location == 0)
|
||||
return;
|
||||
NSRange range = selection;
|
||||
if (selection.length == 0) {
|
||||
NSUInteger location = (selection.location == NSNotFound) ? self.activeModel.text.length - 1
|
||||
: selection.location - 1;
|
||||
range = NSMakeRange(location, 1);
|
||||
}
|
||||
self.activeModel.selectedRange = NSMakeRange(range.location, 0);
|
||||
[self insertText:@"" replacementRange:range]; // Updates edit state
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark NSTextInputClient
|
||||
|
||||
- (void)insertText:(id)string replacementRange:(NSRange)range {
|
||||
if (self.activeModel != nil) {
|
||||
if (range.location == NSNotFound && range.length == 0) {
|
||||
// Use selection
|
||||
range = self.activeModel.selectedRange;
|
||||
}
|
||||
if (range.location > self.activeModel.text.length)
|
||||
range.location = self.activeModel.text.length;
|
||||
if (range.length > (self.activeModel.text.length - range.location))
|
||||
range.length = self.activeModel.text.length - range.location;
|
||||
[self.activeModel.text replaceCharactersInRange:range withString:string];
|
||||
self.activeModel.selectedRange = NSMakeRange(range.location + ((NSString*)string).length, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doCommandBySelector:(SEL)selector {
|
||||
if ([self respondsToSelector:selector]) {
|
||||
// Note: The more obvious [self performSelector...] doesn't give ARC enough information to
|
||||
// handle retain semantics properly. See https://stackoverflow.com/questions/7017281/ for more
|
||||
// information.
|
||||
IMP imp = [self methodForSelector:selector];
|
||||
void (*func)(id, SEL, id) = reinterpret_cast<void (*)(id, SEL, id)>(imp);
|
||||
func(self, selector, nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertNewline:(id)sender {
|
||||
if ([self.activeModel.inputType isEqualToString:kMultilineInputType]) {
|
||||
[self insertText:@"\n" replacementRange:self.activeModel.selectedRange];
|
||||
}
|
||||
[_channel invokeMethod:kPerformAction
|
||||
arguments:@[ _activeClientID, self.activeModel.inputAction ]];
|
||||
}
|
||||
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selectedRange
|
||||
replacementRange:(NSRange)replacementRange {
|
||||
if (self.activeModel != nil) {
|
||||
[self.activeModel.text replaceCharactersInRange:replacementRange withString:string];
|
||||
self.activeModel.selectedRange = selectedRange;
|
||||
[self updateEditState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unmarkText {
|
||||
if (self.activeModel != nil) {
|
||||
self.activeModel.markedRange = NSMakeRange(NSNotFound, 0);
|
||||
[self updateEditState];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRange)selectedRange {
|
||||
return (self.activeModel == nil) ? NSMakeRange(NSNotFound, 0) : self.activeModel.selectedRange;
|
||||
}
|
||||
|
||||
- (NSRange)markedRange {
|
||||
return (self.activeModel == nil) ? NSMakeRange(NSNotFound, 0) : self.activeModel.markedRange;
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText {
|
||||
return (self.activeModel == nil) ? NO : self.activeModel.markedRange.location != NSNotFound;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
if (self.activeModel) {
|
||||
if (actualRange != nil)
|
||||
*actualRange = range;
|
||||
NSString* substring = [self.activeModel.text substringWithRange:range];
|
||||
return [[NSAttributedString alloc] initWithString:substring attributes:nil];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*)validAttributesForMarkedText {
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
|
||||
// TODO: Implement.
|
||||
// Note: This function can't easily be implemented under the system-message architecture.
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point {
|
||||
// TODO: Implement.
|
||||
// Note: This function can't easily be implemented under the system-message architecture.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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 "flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h"
|
||||
|
||||
@implementation FLEView
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark FLEContextHandlingProtocol
|
||||
|
||||
- (void)makeCurrentContext {
|
||||
[self.openGLContext makeCurrentContext];
|
||||
}
|
||||
|
||||
- (void)onPresent {
|
||||
[self.openGLContext flushBuffer];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Implementation
|
||||
|
||||
/**
|
||||
* Declares that the view uses a flipped coordinate system, consistent with Flutter conventions.
|
||||
*/
|
||||
- (BOOL)isFlipped {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)reshape {
|
||||
[_reshapeListener viewDidReshape:self];
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,474 @@
|
||||
// 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 "flutter/shell/platform/darwin/macos/framework/Headers/FLEViewController.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FLEViewController_Internal.h"
|
||||
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h"
|
||||
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEReshapeListener.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FLEView.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FLETextInputPlugin.h"
|
||||
#import "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
static NSString* const kICUBundlePath = @"icudtl.dat";
|
||||
|
||||
static const int kDefaultWindowFramebuffer = 0;
|
||||
|
||||
// Android KeyEvent constants from https://developer.android.com/reference/android/view/KeyEvent
|
||||
static const int kAndroidMetaStateShift = 1 << 0;
|
||||
static const int kAndroidMetaStateAlt = 1 << 1;
|
||||
static const int kAndroidMetaStateCtrl = 1 << 12;
|
||||
static const int kAndroidMetaStateMeta = 1 << 16;
|
||||
|
||||
#pragma mark - Private interface declaration.
|
||||
|
||||
/**
|
||||
* Private interface declaration for FLEViewController.
|
||||
*/
|
||||
@interface FLEViewController ()
|
||||
|
||||
/**
|
||||
* A list of additional responders to keyboard events. Keybord events are forwarded to all of them.
|
||||
*/
|
||||
@property NSMutableOrderedSet<NSResponder*>* additionalKeyResponders;
|
||||
|
||||
/**
|
||||
* Creates and registers plugins used by this view controller.
|
||||
*/
|
||||
- (void)addInternalPlugins;
|
||||
|
||||
/**
|
||||
* Shared implementation of the regular and headless public APIs.
|
||||
*/
|
||||
- (BOOL)launchEngineInternalWithAssetsPath:(nonnull NSURL*)assets
|
||||
headless:(BOOL)headless
|
||||
commandLineArguments:(nullable NSArray<NSString*>*)arguments;
|
||||
|
||||
/**
|
||||
* Creates a render config with callbacks based on whether the embedder is being run as a headless
|
||||
* server.
|
||||
*/
|
||||
+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless;
|
||||
|
||||
/**
|
||||
* Creates the OpenGL context used as the resource context by the engine.
|
||||
*/
|
||||
- (void)createResourceContext;
|
||||
|
||||
/**
|
||||
* Makes the OpenGL context used by the engine for rendering optimization the
|
||||
* current context.
|
||||
*/
|
||||
- (void)makeResourceContextCurrent;
|
||||
|
||||
/**
|
||||
* Responds to system messages sent to this controller from the Flutter engine.
|
||||
*/
|
||||
- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message;
|
||||
|
||||
/**
|
||||
* Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.
|
||||
*/
|
||||
- (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
|
||||
|
||||
/**
|
||||
* Converts |event| to a key event channel message, and sends it to the engine.
|
||||
*/
|
||||
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Static methods provided to engine configuration
|
||||
|
||||
/**
|
||||
* Makes the owned FlutterView the current context.
|
||||
*/
|
||||
static bool OnMakeCurrent(FLEViewController* controller) {
|
||||
[controller.view makeCurrentContext];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current context.
|
||||
*/
|
||||
static bool OnClearCurrent(FLEViewController* controller) {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the GL context as part of the Flutter rendering pipeline.
|
||||
*/
|
||||
static bool OnPresent(FLEViewController* controller) {
|
||||
[controller.view onPresent];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the framebuffer object whose color attachment the engine should render into.
|
||||
*/
|
||||
static uint32_t OnFBO(FLEViewController* controller) {
|
||||
return kDefaultWindowFramebuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the given platform message by dispatching to the controller.
|
||||
*/
|
||||
static void OnPlatformMessage(const FlutterPlatformMessage* message,
|
||||
FLEViewController* controller) {
|
||||
[controller handlePlatformMessage:message];
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the resource context the current context.
|
||||
*/
|
||||
static bool OnMakeResourceCurrent(FLEViewController* controller) {
|
||||
[controller makeResourceContextCurrent];
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark Static methods provided for headless engine configuration
|
||||
|
||||
static bool HeadlessOnMakeCurrent(FLEViewController* controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HeadlessOnClearCurrent(FLEViewController* controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HeadlessOnPresent(FLEViewController* controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t HeadlessOnFBO(FLEViewController* controller) {
|
||||
return kDefaultWindowFramebuffer;
|
||||
}
|
||||
|
||||
static bool HeadlessOnMakeResourceCurrent(FLEViewController* controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma mark - FLEViewController implementation.
|
||||
|
||||
@implementation FLEViewController {
|
||||
FlutterEngine _engine;
|
||||
|
||||
// The additional context provided to the Flutter engine for resource loading.
|
||||
NSOpenGLContext* _resourceContext;
|
||||
|
||||
// A mapping of channel names to the registered handlers for those channels.
|
||||
NSMutableDictionary<NSString*, FlutterBinaryMessageHandler>* _messageHandlers;
|
||||
|
||||
// The plugin used to handle text input. This is not an FLEPlugin, so must be owned separately.
|
||||
FLETextInputPlugin* _textInputPlugin;
|
||||
|
||||
// A message channel for passing key events to the Flutter engine. This should be replaced with
|
||||
// an embedding API; see Issue #47.
|
||||
FlutterBasicMessageChannel* _keyEventChannel;
|
||||
}
|
||||
|
||||
@dynamic view;
|
||||
|
||||
/**
|
||||
* Performs initialization that's common between the different init paths.
|
||||
*/
|
||||
static void CommonInit(FLEViewController* controller) {
|
||||
controller->_messageHandlers = [[NSMutableDictionary alloc] init];
|
||||
controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder*)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self != nil) {
|
||||
CommonInit(self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
if (self != nil) {
|
||||
CommonInit(self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (FlutterEngineShutdown(_engine) == kSuccess) {
|
||||
_engine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
self.view = [[FLEView alloc] init];
|
||||
}
|
||||
|
||||
#pragma mark - Public methods
|
||||
|
||||
- (BOOL)launchEngineWithAssetsPath:(NSURL*)assets
|
||||
commandLineArguments:(NSArray<NSString*>*)arguments {
|
||||
return [self launchEngineInternalWithAssetsPath:assets
|
||||
headless:NO
|
||||
commandLineArguments:arguments];
|
||||
}
|
||||
|
||||
- (BOOL)launchHeadlessEngineWithAssetsPath:(NSURL*)assets
|
||||
commandLineArguments:(NSArray<NSString*>*)arguments {
|
||||
return [self launchEngineInternalWithAssetsPath:assets
|
||||
headless:YES
|
||||
commandLineArguments:arguments];
|
||||
}
|
||||
|
||||
- (id<FLEPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
|
||||
// Currently, the view controller acts as the registrar for all plugins, so the
|
||||
// name is ignored. It is part of the API to reduce churn in the future when
|
||||
// aligning more closely with the Flutter registrar system.
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Framework-internal methods
|
||||
|
||||
- (void)addKeyResponder:(NSResponder*)responder {
|
||||
[self.additionalKeyResponders addObject:responder];
|
||||
}
|
||||
|
||||
- (void)removeKeyResponder:(NSResponder*)responder {
|
||||
[self.additionalKeyResponders removeObject:responder];
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
- (void)addInternalPlugins {
|
||||
_textInputPlugin = [[FLETextInputPlugin alloc] initWithViewController:self];
|
||||
_keyEventChannel =
|
||||
[FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent"
|
||||
binaryMessenger:self
|
||||
codec:[FlutterJSONMessageCodec sharedInstance]];
|
||||
}
|
||||
|
||||
- (BOOL)launchEngineInternalWithAssetsPath:(NSURL*)assets
|
||||
headless:(BOOL)headless
|
||||
commandLineArguments:(NSArray<NSString*>*)arguments {
|
||||
if (_engine != NULL) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Set up the resource context. This is done here rather than in viewDidLoad as there's no
|
||||
// guarantee that viewDidLoad will be called before the engine is started, and the context must
|
||||
// be valid by that point.
|
||||
[self createResourceContext];
|
||||
|
||||
const FlutterRendererConfig config = [FLEViewController createRenderConfigHeadless:headless];
|
||||
|
||||
// Register internal plugins before starting the engine.
|
||||
[self addInternalPlugins];
|
||||
|
||||
// FlutterProjectArgs is expecting a full argv, so when processing it for flags the first
|
||||
// item is treated as the executable and ignored. Add a dummy value so that all provided arguments
|
||||
// are used.
|
||||
const unsigned long argc = arguments.count + 1;
|
||||
const char** argv = (const char**)malloc(argc * sizeof(const char*));
|
||||
argv[0] = "placeholder";
|
||||
for (NSUInteger i = 0; i < arguments.count; ++i) {
|
||||
argv[i + 1] = [arguments[i] UTF8String];
|
||||
}
|
||||
|
||||
NSString* icuData = [[NSBundle bundleForClass:[self class]] pathForResource:kICUBundlePath
|
||||
ofType:nil];
|
||||
|
||||
FlutterProjectArgs flutterArguments = {};
|
||||
flutterArguments.struct_size = sizeof(FlutterProjectArgs);
|
||||
flutterArguments.assets_path = assets.fileSystemRepresentation;
|
||||
flutterArguments.icu_data_path = icuData.UTF8String;
|
||||
flutterArguments.command_line_argc = (int)(argc);
|
||||
flutterArguments.command_line_argv = argv;
|
||||
flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
|
||||
|
||||
FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &flutterArguments,
|
||||
(__bridge void*)(self), &_engine);
|
||||
free(argv);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to start Flutter engine: error %d", result);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (FlutterRendererConfig)createRenderConfigHeadless:(BOOL)headless {
|
||||
if (headless) {
|
||||
const FlutterRendererConfig config = {
|
||||
.type = kOpenGL,
|
||||
.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig),
|
||||
.open_gl.make_current = (BoolCallback)HeadlessOnMakeCurrent,
|
||||
.open_gl.clear_current = (BoolCallback)HeadlessOnClearCurrent,
|
||||
.open_gl.present = (BoolCallback)HeadlessOnPresent,
|
||||
.open_gl.fbo_callback = (UIntCallback)HeadlessOnFBO,
|
||||
.open_gl.make_resource_current = (BoolCallback)HeadlessOnMakeResourceCurrent};
|
||||
return config;
|
||||
} else {
|
||||
const FlutterRendererConfig config = {
|
||||
.type = kOpenGL,
|
||||
.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig),
|
||||
.open_gl.make_current = (BoolCallback)OnMakeCurrent,
|
||||
.open_gl.clear_current = (BoolCallback)OnClearCurrent,
|
||||
.open_gl.present = (BoolCallback)OnPresent,
|
||||
.open_gl.fbo_callback = (UIntCallback)OnFBO,
|
||||
.open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent};
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createResourceContext {
|
||||
NSOpenGLContext* viewContext = ((NSOpenGLView*)self.view).openGLContext;
|
||||
_resourceContext = [[NSOpenGLContext alloc] initWithFormat:viewContext.pixelFormat
|
||||
shareContext:viewContext];
|
||||
}
|
||||
|
||||
- (void)makeResourceContextCurrent {
|
||||
[_resourceContext makeCurrentContext];
|
||||
}
|
||||
|
||||
- (void)handlePlatformMessage:(const FlutterPlatformMessage*)message {
|
||||
NSData* messageData = [NSData dataWithBytesNoCopy:(void*)message->message
|
||||
length:message->message_size
|
||||
freeWhenDone:NO];
|
||||
NSString* channel = @(message->channel);
|
||||
__block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
|
||||
|
||||
FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
|
||||
if (responseHandle) {
|
||||
FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle,
|
||||
static_cast<const uint8_t*>(response.bytes),
|
||||
response.length);
|
||||
responseHandle = NULL;
|
||||
} else {
|
||||
NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
|
||||
"on channel '%@'.",
|
||||
channel);
|
||||
}
|
||||
};
|
||||
|
||||
FlutterBinaryMessageHandler channelHandler = _messageHandlers[channel];
|
||||
if (channelHandler) {
|
||||
channelHandler(messageData, binaryResponseHandler);
|
||||
} else {
|
||||
binaryResponseHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
|
||||
NSPoint locationInView = [self.view convertPoint:event.locationInWindow fromView:nil];
|
||||
NSPoint locationInBackingCoordinates = [self.view convertPointToBacking:locationInView];
|
||||
const FlutterPointerEvent flutterEvent = {
|
||||
.struct_size = sizeof(flutterEvent),
|
||||
.phase = phase,
|
||||
.x = locationInBackingCoordinates.x,
|
||||
.y = -locationInBackingCoordinates.y, // convertPointToBacking makes this negative.
|
||||
.timestamp = static_cast<size_t>(event.timestamp * NSEC_PER_MSEC),
|
||||
};
|
||||
FlutterEngineSendPointerEvent(_engine, &flutterEvent, 1);
|
||||
}
|
||||
|
||||
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type {
|
||||
[_keyEventChannel sendMessage:@{
|
||||
@"keymap" : @"android",
|
||||
@"type" : type,
|
||||
@"keyCode" : @(event.keyCode),
|
||||
@"metaState" :
|
||||
@(((event.modifierFlags & NSEventModifierFlagShift) ? kAndroidMetaStateShift : 0) |
|
||||
((event.modifierFlags & NSEventModifierFlagOption) ? kAndroidMetaStateAlt : 0) |
|
||||
((event.modifierFlags & NSEventModifierFlagControl) ? kAndroidMetaStateCtrl : 0) |
|
||||
((event.modifierFlags & NSEventModifierFlagCommand) ? kAndroidMetaStateMeta : 0))
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - FLEReshapeListener
|
||||
|
||||
/**
|
||||
* Responds to view reshape by notifying the engine of the change in dimensions.
|
||||
*/
|
||||
- (void)viewDidReshape:(NSOpenGLView*)view {
|
||||
CGRect scaledBounds = [view convertRectToBacking:view.bounds];
|
||||
const FlutterWindowMetricsEvent event = {
|
||||
.struct_size = sizeof(event),
|
||||
.width = static_cast<size_t>(scaledBounds.size.width),
|
||||
.height = static_cast<size_t>(scaledBounds.size.height),
|
||||
.pixel_ratio = scaledBounds.size.width / view.bounds.size.width,
|
||||
};
|
||||
FlutterEngineSendWindowMetricsEvent(_engine, &event);
|
||||
}
|
||||
|
||||
#pragma mark - FlutterBinaryMessenger
|
||||
|
||||
- (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message {
|
||||
FlutterPlatformMessage platformMessage = {
|
||||
.struct_size = sizeof(FlutterPlatformMessage),
|
||||
.channel = [channel UTF8String],
|
||||
.message = static_cast<const uint8_t*>(message.bytes),
|
||||
.message_size = message.length,
|
||||
};
|
||||
|
||||
FlutterEngineResult result = FlutterEngineSendPlatformMessage(_engine, &platformMessage);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel, result);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMessageHandlerOnChannel:(nonnull NSString*)channel
|
||||
binaryMessageHandler:(nullable FlutterBinaryMessageHandler)handler {
|
||||
_messageHandlers[channel] = [handler copy];
|
||||
}
|
||||
|
||||
#pragma mark - FLEPluginRegistrar
|
||||
|
||||
- (id<FlutterBinaryMessenger>)messenger {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addMethodCallDelegate:(nonnull id<FLEPlugin>)delegate
|
||||
channel:(nonnull FlutterMethodChannel*)channel {
|
||||
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
||||
[delegate handleMethodCall:call result:result];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - NSResponder
|
||||
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent*)event {
|
||||
[self dispatchKeyEvent:event ofType:@"keydown"];
|
||||
for (NSResponder* responder in self.additionalKeyResponders) {
|
||||
if ([responder respondsToSelector:@selector(keyDown:)]) {
|
||||
[responder keyDown:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent*)event {
|
||||
[self dispatchKeyEvent:event ofType:@"keyup"];
|
||||
for (NSResponder* responder in self.additionalKeyResponders) {
|
||||
if ([responder respondsToSelector:@selector(keyUp:)]) {
|
||||
[responder keyUp:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent*)event {
|
||||
[self dispatchMouseEvent:event phase:kDown];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent*)event {
|
||||
[self dispatchMouseEvent:event phase:kUp];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent*)event {
|
||||
[self dispatchMouseEvent:event phase:kMove];
|
||||
}
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user