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/ios_surface_software.mm
|
||||||
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h
|
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/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/EmbedderInfo.plist
|
||||||
FILE: ../../../flutter/shell/platform/embedder/assets/embedder.modulemap
|
FILE: ../../../flutter/shell/platform/embedder/assets/embedder.modulemap
|
||||||
FILE: ../../../flutter/shell/platform/embedder/embedder.cc
|
FILE: ../../../flutter/shell/platform/embedder/embedder.cc
|
||||||
|
|||||||
@@ -4,12 +4,19 @@
|
|||||||
|
|
||||||
assert(is_mac || is_ios)
|
assert(is_mac || is_ios)
|
||||||
|
|
||||||
|
import("framework_shared.gni")
|
||||||
|
|
||||||
group("darwin") {
|
group("darwin") {
|
||||||
if (is_ios) {
|
if (is_ios) {
|
||||||
deps = [
|
deps = [
|
||||||
"ios:flutter_framework",
|
"ios:flutter_framework",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
if (is_mac) {
|
||||||
|
deps = [
|
||||||
|
"macos:flutter_framework",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source_set("flutter_channels") {
|
source_set("flutter_channels") {
|
||||||
@@ -40,6 +47,22 @@ source_set("flutter_channels") {
|
|||||||
public_configs = [ "$flutter_root:config" ]
|
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") {
|
executable("flutter_channels_unittests") {
|
||||||
testonly = true
|
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
|
- (void)sendOnChannel:(NSString*)channel
|
||||||
message:(NSData* _Nullable)message
|
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
|
* 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 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.
|
* @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.
|
* Registers a message handler with this channel.
|
||||||
@@ -234,7 +238,10 @@ FLUTTER_EXPORT
|
|||||||
*/
|
*/
|
||||||
- (void)invokeMethod:(NSString*)method
|
- (void)invokeMethod:(NSString*)method
|
||||||
arguments:(id _Nullable)arguments
|
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.
|
* 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