diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9c211f9916270..743bcefa688c5 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1094,6 +1094,13 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouse FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRendererTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 4085e39f73e9f..b9a942a82a869 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -77,6 +77,9 @@ source_set("flutter_framework_source") { "framework/Source/FlutterMouseCursorPlugin.mm", "framework/Source/FlutterOpenGLRenderer.h", "framework/Source/FlutterOpenGLRenderer.mm", + "framework/Source/FlutterPlatformViewController.mm", + "framework/Source/FlutterPlatformViewController_Internal.h", + "framework/Source/FlutterPlatformViews.h", "framework/Source/FlutterRenderer.h", "framework/Source/FlutterResizableBackingStoreProvider.h", "framework/Source/FlutterResizableBackingStoreProvider.mm", @@ -147,6 +150,9 @@ executable("flutter_desktop_darwin_unittests") { sources = [ "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", + "framework/Source/FlutterPlatformViewControllerTest.mm", + "framework/Source/FlutterPlatformViewMock.h", + "framework/Source/FlutterPlatformViewMock.mm", "framework/Source/FlutterViewControllerTest.mm", "framework/Source/FlutterViewControllerTestUtils.h", "framework/Source/FlutterViewControllerTestUtils.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 626197ed1ec9e..1de1542ff8356 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -14,6 +14,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" /** @@ -57,6 +58,16 @@ - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)t */ - (void)loadAOTData:(NSString*)assetsDir; +/** + * Creates and returns a FlutterCompositor* to be used by the embedder. + */ +- (FlutterCompositor*)createFlutterCompositor; + +/** + * Create a platform view channel and setup a method call handler. + */ +- (void)setupPlatformViewChannel; + @end #pragma mark - @@ -141,6 +152,13 @@ @implementation FlutterEngine { // FlutterCompositor is copied and used in embedder.cc. FlutterCompositor _compositor; + + // A method channel for platform view functionality. + FlutterMethodChannel* _platformViewsChannel; + + // Used to support creation and deletion of platform views and + // registering platform view factories. + FlutterPlatformViewController* _platformViewController; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -248,6 +266,9 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.aot_data = _aotData; } + [self setupPlatformViewChannel]; + _platformViewController = [[FlutterPlatformViewController alloc] init]; + flutterArguments.compositor = [self createFlutterCompositor]; FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; @@ -428,6 +449,10 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event { _embedderAPI.SendPointerEvent(_engine, &event, 1); } +- (FlutterPlatformViewController*)platformViewController { + return _platformViewController; +} + #pragma mark - Private methods - (void)sendUserLocales { @@ -504,6 +529,67 @@ - (void)shutDownEngine { _engine = nullptr; } +- (FlutterCompositor*)createFlutterCompositor { + // TODO(richardjcai): Add support for creating a FlutterGLCompositor + // with a nil _viewController for headless engines. + // https://github.com/flutter/flutter/issues/71606 + if (_viewController == nullptr) { + return nullptr; + } + + [_mainOpenGLContext makeCurrentContext]; + + _macOSGLCompositor = + std::make_unique(_viewController, _platformViewController); + + _compositor = {}; + _compositor.struct_size = sizeof(FlutterCompositor); + _compositor.user_data = _macOSGLCompositor.get(); + + _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, // + FlutterBackingStore* backing_store_out, // + void* user_data // + ) { + return reinterpret_cast(user_data)->CreateBackingStore( + config, backing_store_out); + }; + + _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // + void* user_data // + ) { + return reinterpret_cast(user_data)->CollectBackingStore( + backing_store); + }; + + _compositor.present_layers_callback = [](const FlutterLayer** layers, // + size_t layers_count, // + void* user_data // + ) { + return reinterpret_cast(user_data)->Present(layers, + layers_count); + }; + + __weak FlutterEngine* weakSelf = self; + _macOSGLCompositor->SetPresentCallback( + [weakSelf]() { return [weakSelf engineCallbackOnPresent]; }); + + _compositor.avoid_backing_store_cache = true; + + return &_compositor; +} + +- (void)setupPlatformViewChannel { + _platformViewsChannel = + [FlutterMethodChannel methodChannelWithName:@"flutter/platform_views" + binaryMessenger:self.binaryMessenger + codec:[FlutterStandardMethodCodec sharedInstance]]; + + __weak FlutterEngine* weakSelf = self; + [_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [[weakSelf platformViewController] handleMethodCall:call result:result]; + }]; +} + #pragma mark - FlutterBinaryMessenger - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { @@ -610,11 +696,11 @@ - (BOOL)unregisterTextureWithID:(int64_t)textureID { - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime { const auto engine_time = _embedderAPI.GetCurrentTime(); - __weak FlutterEngine* weak_self = self; + __weak FlutterEngine* weakSelf = self; auto worker = ^{ - FlutterEngine* strong_self = weak_self; - if (strong_self && strong_self->_engine) { - auto result = _embedderAPI.RunTask(strong_self->_engine, &task); + FlutterEngine* strongSelf = weakSelf; + if (strongSelf && strongSelf->_engine) { + auto result = _embedderAPI.RunTask(strongSelf->_engine, &task); if (result != kSuccess) { NSLog(@"Could not post a task to the Flutter engine."); } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h index 8f0be73999a08..81f7c6e5e8c85 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h @@ -5,6 +5,8 @@ #include #include "flutter/fml/macros.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -50,6 +52,7 @@ class FlutterGLCompositor { private: const FlutterViewController* view_controller_; + const FlutterPlatformViewController* platform_view_controller_; const NSOpenGLContext* open_gl_context_; PresentCallback present_callback_; @@ -67,6 +70,15 @@ class FlutterGLCompositor { // created for the frame. bool frame_started_ = false; + // Update the backing CALayer using the backing store's specifications. + void PresentBackingStoreContent( + FlutterBackingStoreData* flutter_backing_store_data, + size_t layer_position); + + // Add the Platform View's content to the FlutterView at depth + // layer_position. + void PresentPlatformView(const FlutterLayer* layer, size_t layer_position); + // Set frame_started_ to true and reset all layer state. void StartFrame(); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm index 125b90bc07e74..354c6fc3eedaf 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm @@ -23,7 +23,11 @@ NSOpenGLContext* opengl_context) : open_gl_context_(opengl_context) { FML_CHECK(view_controller != nullptr) << "FlutterViewController* cannot be nullptr"; + FML_CHECK(platform_view_controller != nullptr) + << "FlutterPlatformViewController* cannot be nullptr"; + view_controller_ = view_controller; + platform_view_controller_ = platform_view_controller; } bool FlutterGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* config, @@ -75,35 +79,21 @@ } bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { + [platform_view_controller_ disposePlatformViews]; for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; FlutterBackingStore* backing_store = const_cast(layer->backing_store); switch (layer->type) { case kFlutterLayerContentTypeBackingStore: { if (backing_store->open_gl.framebuffer.user_data) { - FlutterBackingStoreData* backing_store_data = + FlutterBackingStoreData* flutter_backing_store_data = (__bridge FlutterBackingStoreData*)backing_store->open_gl.framebuffer.user_data; - - FlutterIOSurfaceHolder* io_surface_holder = [backing_store_data ioSurfaceHolder]; - size_t layer_id = [backing_store_data layerId]; - - CALayer* content_layer = ca_layer_map_[layer_id]; - - FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; - - content_layer.frame = content_layer.superlayer.bounds; - - // The surface is an OpenGL texture, which means it has origin in bottom left corner - // and needs to be flipped vertically - content_layer.transform = CATransform3DMakeScale(1, -1, 1); - IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface]; - [content_layer setContents:(__bridge id)io_surface_contents]; + PresentBackingStoreContent(flutter_backing_store_data, i); } break; } case kFlutterLayerContentTypePlatformView: - // Add functionality in follow up PR. - FML_LOG(WARNING) << "Presenting PlatformViews not yet supported"; + PresentPlatformView(layer, i); break; }; } @@ -113,6 +103,48 @@ return present_callback_(); } +void FlutterGLCompositor::PresentBackingStoreContent( + FlutterBackingStoreData* flutter_backing_store_data, + size_t layer_position) { + FML_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to update CALayer contents"; + + FlutterIOSurfaceHolder* io_surface_holder = [flutter_backing_store_data ioSurfaceHolder]; + size_t layer_id = [flutter_backing_store_data layerId]; + + CALayer* content_layer = ca_layer_map_[layer_id]; + + FML_DCHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; + + content_layer.frame = content_layer.superlayer.bounds; + content_layer.zPosition = layer_position; + + // The surface is an OpenGL texture, which means it has origin in bottom left corner + // and needs to be flipped vertically + content_layer.transform = CATransform3DMakeScale(1, -1, 1); + IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface]; + [content_layer setContents:(__bridge id)io_surface_contents]; +} + +void FlutterGLCompositor::PresentPlatformView(const FlutterLayer* layer, size_t layer_position) { + FML_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle presenting platform views"; + + FML_DCHECK(platform_view_controller_.platformViews.count(layer->platform_view->identifier)) + << "Platform view not found for id: " << layer->platform_view->identifier; + + NSView* platform_view = platform_view_controller_.platformViews[layer->platform_view->identifier]; + + CGFloat scale = [[NSScreen mainScreen] backingScaleFactor]; + platform_view.frame = CGRectMake(layer->offset.x / scale, layer->offset.y / scale, + layer->size.width / scale, layer->size.height / scale); + if (platform_view.superview == nil) { + [view_controller_.flutterView addSubview:platform_view]; + } else { + platform_view.layer.zPosition = layer_position; + } +} + void FlutterGLCompositor::SetPresentCallback( const FlutterGLCompositor::PresentCallback& present_callback) { present_callback_ = present_callback; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm index 25fc35575176f..b783307c7e45b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm @@ -5,6 +5,7 @@ #import #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" #import "flutter/testing/testing.h" @@ -12,9 +13,11 @@ TEST(FlutterGLCompositorTest, TestPresent) { id mockViewController = CreateMockViewController(nil); + FlutterPlatformViewController* platformViewController = + [[FlutterPlatformViewController alloc] init]; std::unique_ptr macos_compositor = - std::make_unique(mockViewController, nullptr); + std::make_unique(mockViewController, platformViewController); bool flag = false; macos_compositor->SetPresentCallback([f = &flag]() { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm new file mode 100644 index 0000000000000..c3f995c5ea7c6 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm @@ -0,0 +1,93 @@ +// 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. + +#include "flutter/fml/logging.h" + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h" + +@implementation FlutterPlatformViewController + +- (instancetype)init { + self = [super init]; + if (self) { + _platformViewFactories = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)onCreateWithViewId:(int64_t)viewId + viewType:(nonnull NSString*)viewType + result:(nonnull FlutterResult)result { + if (_platformViews.count(viewId) != 0) { + result([FlutterError errorWithCode:@"recreating_view" + message:@"trying to create an already created view" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + } + + NSObject* factory = _platformViewFactories[viewType]; + if (factory == nil) { + result([FlutterError + errorWithCode:@"unregistered_view_type" + message:@"trying to create a view with an unregistered type" + details:[NSString stringWithFormat:@"unregistered view type: '%@'", viewType]]); + return; + } + + NSObject* platform_view = [factory createWithFrame:CGRectZero + viewIdentifier:viewId + arguments:nil]; + + _platformViews[viewId] = [platform_view view]; + result(nil); +} + +- (void)onDisposeWithViewId:(int64_t)viewId result:(nonnull FlutterResult)result { + if (_platformViews.count(viewId) == 0) { + result([FlutterError errorWithCode:@"unknown_view" + message:@"trying to dispose an unknown" + details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]); + return; + } + + // The following disposePlatformViews call will dispose the views. + _platformViewsToDispose.insert(viewId); + result(nil); +} + +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId { + _platformViewFactories[factoryId] = factory; +} + +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result { + if ([[call method] isEqualToString:@"create"]) { + NSMutableDictionary* args = [call arguments]; + int64_t viewId = [args[@"id"] longValue]; + NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])]; + [self onCreateWithViewId:viewId viewType:viewType result:result]; + } else if ([[call method] isEqualToString:@"dispose"]) { + NSNumber* arg = [call arguments]; + int64_t viewId = [arg longLongValue]; + [self onDisposeWithViewId:viewId result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)disposePlatformViews { + if (_platformViewsToDispose.empty()) { + return; + } + + FML_DCHECK([[NSThread currentThread] isMainThread]) + << "Must be on the main thread to handle disposing platform views"; + for (int64_t viewId : _platformViewsToDispose) { + NSView* view = _platformViews[viewId]; + [view removeFromSuperview]; + _platformViews.erase(viewId); + } + _platformViewsToDispose.clear(); +} + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm new file mode 100644 index 0000000000000..e20d9d4091522 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewControllerTest.mm @@ -0,0 +1,128 @@ +// 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/FlutterPlatformViewController_Internal.h" + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h" + +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +TEST(FlutterPlatformViewController, TestCreatePlatformViewNoMatchingViewType) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"FlutterPlatformViewMock" + }]; + + __block bool errored = false; + FlutterResult result = ^(id result) { + if ([result isKindOfClass:[FlutterError class]]) { + errored = true; + } + }; + + [platformViewController handleMethodCall:methodCall result:result]; + + // We expect the call to error since no factories are registered. + EXPECT_TRUE(errored); +} + +TEST(FlutterPlatformViewController, TestRegisterPlatformViewFactoryAndCreate) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory alloc]; + + [platformViewController registerViewFactory:factory withId:@"MockPlatformView"]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockPlatformView" + }]; + + __block bool success = false; + FlutterResult result = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + success = true; + } + }; + [platformViewController handleMethodCall:methodCall result:result]; + + EXPECT_TRUE(success); +} + +TEST(FlutterPlatformViewController, TestCreateAndDispose) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterPlatformViewMockFactory* factory = [FlutterPlatformViewMockFactory alloc]; + + [platformViewController registerViewFactory:factory withId:@"MockPlatformView"]; + + FlutterMethodCall* methodCallOnCreate = + [FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockPlatformView" + }]; + + __block bool created = false; + FlutterResult resultOnCreate = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + created = true; + } + }; + + [platformViewController handleMethodCall:methodCallOnCreate result:resultOnCreate]; + + FlutterMethodCall* methodCallOnDispose = + [FlutterMethodCall methodCallWithMethodName:@"dispose" + arguments:[NSNumber numberWithLongLong:2]]; + + __block bool disposed = false; + FlutterResult resultOnDispose = ^(id result) { + // If a platform view is successfully created, the result is nil. + if (result == nil) { + disposed = true; + } + }; + + [platformViewController handleMethodCall:methodCallOnDispose result:resultOnDispose]; + + EXPECT_TRUE(created); + EXPECT_TRUE(disposed); +} + +TEST(FlutterPlatformViewController, TestDisposeOnMissingViewId) { + // Use id so we can access handleMethodCall method. + id platformViewController = [[FlutterPlatformViewController alloc] init]; + + FlutterMethodCall* methodCall = + [FlutterMethodCall methodCallWithMethodName:@"dispose" + arguments:[NSNumber numberWithLongLong:20]]; + + __block bool errored = false; + FlutterResult result = ^(id result) { + if ([result isKindOfClass:[FlutterError class]]) { + errored = true; + } + }; + + [platformViewController handleMethodCall:methodCall result:result]; + + EXPECT_TRUE(errored); +} + +} // flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h new file mode 100644 index 0000000000000..7e82e0fa3fa4f --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController_Internal.h @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import "FlutterChannels.h" + +#include +#include + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + +@interface FlutterPlatformViewController : NSViewController +@end + +@interface FlutterPlatformViewController () + +// NSDictionary maps strings to FlutterPlatformViewFactorys. +@property(nonnull, nonatomic) + NSMutableDictionary*>* platformViewFactories; + +// A map of platform view ids to views. +@property(nonatomic) std::map platformViews; + +// View ids that are going to be disposed on the next present call. +@property(nonatomic) std::unordered_set platformViewsToDispose; + +/** + * Creates a platform view of viewType with viewId. + * FlutterResult is updated to contain nil for success or to contain + * a FlutterError if there is an error. + */ +- (void)onCreateWithViewId:(int64_t)viewId + viewType:(nonnull NSString*)viewType + result:(nonnull FlutterResult)result; + +/** + * Disposes the platform view with id viewId. + * FlutterResult is updated to contain nil for success or a FlutterError if there is an error. + */ +- (void)onDisposeWithViewId:(int64_t)viewId result:(nonnull FlutterResult)result; + +/** + * Register a view factory by adding an entry into the platformViewFactories map with key factoryId + * and value factory. + */ +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId; + +- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Remove platform views who's ids are in the platformViewsToDispose set. + * Called before a new frame is presented. + */ +- (void)disposePlatformViews; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h new file mode 100644 index 0000000000000..796e67389b03e --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + +@interface FlutterPlatformViewMock : NSObject +@property(nonatomic, strong) NSView* view; +@end + +@interface MockPlatformView : NSView +@end + +@interface FlutterPlatformViewMockFactory : NSObject +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm new file mode 100644 index 0000000000000..9c2b891e794cb --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.mm @@ -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 +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewMock.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" + +@implementation MockPlatformView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + return self; +} + +@end + +@implementation FlutterPlatformViewMock + +- (instancetype)initWithFrame:(CGRect)frame arguments:(id _Nullable)args { + if (self = [super init]) { + _view = [[MockPlatformView alloc] initWithFrame:frame]; + } + return self; +} + +@end + +@implementation FlutterPlatformViewMockFactory +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args { + return [[FlutterPlatformViewMock alloc] initWithFrame:frame arguments:args]; +} + +- (NSObject*)createArgsCodec { + return [FlutterStandardMessageCodec sharedInstance]; +} + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h new file mode 100644 index 0000000000000..d0b415c62876f --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef FLUTTER_FLUTTERPLATFORMVIEWS_H_ +#define FLUTTER_FLUTTERPLATFORMVIEWS_H_ + +#import + +#import "FlutterCodecs.h" +#import "FlutterMacros.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wraps a `NSView` for embedding in the Flutter hierarchy + */ +@protocol FlutterPlatformView +/** + * Returns a reference to the `NSView` that is wrapped by this `FlutterPlatformView`. + * + * It is recommended to return a cached view instance in this method. + * Constructing and returning a new NSView instance in this method might cause undefined behavior. + * + * TODO(richardjcai): Prevent [FlutterPlatformView view] to be called multiple times + * in a single frame. + */ +- (NSView*)view; +@end + +FLUTTER_EXPORT +@protocol FlutterPlatformViewFactory +/** + * Create a `FlutterPlatformView`. + * + * Implemented by MacOS code that expose a `FlutterPlatformView` for embedding in a Flutter app. + * + * The implementation of this method should create a new `FlutterPlatformView` and return it. + * + * @param frame The rectangle for the newly created `FlutterPlatformView` measured in points. + * @param viewId A unique identifier for this `FlutterPlatformView`. + * @param args Parameters for creating the `FlutterPlatformView` sent from the Dart side of the + * Flutter app. If `createArgsCodec` is not implemented, or if no creation arguments were sent from + * the Dart code, this will be null. Otherwise this will be the value sent from the Dart code as + * decoded by `createArgsCodec`. + */ +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args; + +/** + * Returns the `FlutterMessageCodec` for decoding the args parameter of `createWithFrame`. + * + * Only needs to be implemented if `createWithFrame` needs an arguments parameter. + */ +@optional +- (NSObject*)createArgsCodec; +@end + +NS_ASSUME_NONNULL_END + +#endif // FLUTTER_FLUTTERPLATFORMVIEWS_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index acef9f23bae5e..7745b9e407830 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -3,8 +3,8 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" - #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @interface FlutterViewController () @@ -18,8 +18,39 @@ @property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard; /** - * Adds an intermediate responder for keyboard events. Key up and key down events are forwarded to - * all added responders, and they either handle the keys or not. + * Platform View Methods. + */ + +/** + * Creates a platform view using the arguments from the provided call. + * The call's arguments should be castable to an NSMutableDictionary* + * and the dictionary should at least hold one key for "id" that maps to the view id and + * one key for "viewType" which maps to the view type (string) that was used to register + * the factory. + * FlutterResult is updated to contain nil for success or to contain + * a FlutterError if there is an error. + */ +- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Disposes a platform view using the arguments from the provided call. + * The call's arguments should be the Id (castable to NSNumber*) of the platform view + * that should be disposed. + * FlutterResult is updated to contain nil for success or a FlutterError if there is an error. + */ +- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; + +/** + * Register a view factory by adding an entry into the factories_ map with key factoryId + * and value factory. + */ +- (void)registerViewFactory:(nonnull NSObject*)factory + withId:(nonnull NSString*)factoryId; + + +/** + * Adds a responder for keyboard events. Key up and key down events are forwarded to all added + * responders. */ - (void)addKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder;