Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 182b975

Browse files
committed
Add MacOS platform view support to FlutterViewController
Add platform view support in FlutterGLCompositor and FlutterViewController
1 parent 494b66a commit 182b975

File tree

7 files changed

+238
-19
lines changed

7 files changed

+238
-19
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSur
10721072
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm
10731073
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
10741074
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
1075+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h
10751076
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h
10761077
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm
10771078
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h

shell/platform/darwin/macos/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ source_set("flutter_framework_source") {
6262
"framework/Source/FlutterIOSurfaceHolder.mm",
6363
"framework/Source/FlutterMouseCursorPlugin.h",
6464
"framework/Source/FlutterMouseCursorPlugin.mm",
65+
"framework/Source/FlutterPlatformViews.h",
6566
"framework/Source/FlutterResizeSynchronizer.h",
6667
"framework/Source/FlutterResizeSynchronizer.mm",
6768
"framework/Source/FlutterSurfaceManager.h",

shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <map>
66

77
#include "flutter/fml/macros.h"
8+
#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h"
89
#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
910
#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
1011
#include "flutter/shell/platform/embedder/embedder.h"
@@ -66,9 +67,17 @@ class FlutterGLCompositor {
6667
// created for the frame.
6768
bool frame_started_ = false;
6869

70+
// Update the backing CALayer using the backing store's specifications.
71+
void PresentBackingStoreContent(
72+
FlutterBackingStoreData* flutter_backing_store_data,
73+
size_t layer_position);
74+
6975
// Set frame_started_ to true and reset all layer state.
7076
void StartFrame();
7177

78+
// Remove platform views that are specified for deletion.
79+
void DisposePlatformViews();
80+
7281
// Creates a CALayer and adds it to ca_layer_map_ and increments
7382
// ca_layer_count_; Returns the key value (size_t) for the layer in
7483
// ca_layer_map_.

shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,35 +71,31 @@
7171
}
7272

7373
bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) {
74+
DisposePlatformViews();
7475
for (size_t i = 0; i < layers_count; ++i) {
7576
const auto* layer = layers[i];
7677
FlutterBackingStore* backing_store = const_cast<FlutterBackingStore*>(layer->backing_store);
7778
switch (layer->type) {
7879
case kFlutterLayerContentTypeBackingStore: {
7980
if (backing_store->open_gl.framebuffer.user_data) {
80-
FlutterBackingStoreData* backing_store_data =
81+
FlutterBackingStoreData* flutter_backing_store_data =
8182
(__bridge FlutterBackingStoreData*)backing_store->open_gl.framebuffer.user_data;
82-
83-
FlutterIOSurfaceHolder* io_surface_holder = [backing_store_data ioSurfaceHolder];
84-
size_t layer_id = [backing_store_data layerId];
85-
86-
CALayer* content_layer = ca_layer_map_[layer_id];
87-
88-
FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id;
89-
90-
content_layer.frame = content_layer.superlayer.bounds;
91-
92-
// The surface is an OpenGL texture, which means it has origin in bottom left corner
93-
// and needs to be flipped vertically
94-
content_layer.transform = CATransform3DMakeScale(1, -1, 1);
95-
IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface];
96-
[content_layer setContents:(__bridge id)io_surface_contents];
83+
PresentBackingStoreContent(flutter_backing_store_data, i);
9784
}
9885
break;
9986
}
10087
case kFlutterLayerContentTypePlatformView:
101-
// Add functionality in follow up PR.
102-
FML_LOG(WARNING) << "Presenting PlatformViews not yet supported";
88+
FML_CHECK([[NSThread currentThread] isMainThread])
89+
<< "Must be on the main thread to handle presenting platform views";
90+
NSView* platform_view = view_controller_.platformViews[layer->platform_view->identifier];
91+
CGFloat scale = [[NSScreen mainScreen] backingScaleFactor];
92+
platform_view.frame = CGRectMake(layer->offset.x / scale, layer->offset.y / scale,
93+
layer->size.width / scale, layer->size.height / scale);
94+
if (platform_view.superview == nil) {
95+
[view_controller_.flutterView addSubview:platform_view];
96+
} else {
97+
platform_view.layer.zPosition = i;
98+
}
10399
break;
104100
};
105101
}
@@ -109,6 +105,29 @@
109105
return present_callback_();
110106
}
111107

108+
void FlutterGLCompositor::PresentBackingStoreContent(
109+
FlutterBackingStoreData* flutter_backing_store_data,
110+
size_t layer_position) {
111+
FML_CHECK([[NSThread currentThread] isMainThread])
112+
<< "Must be on the main thread to update CALayer contents";
113+
114+
FlutterIOSurfaceHolder* io_surface_holder = [flutter_backing_store_data ioSurfaceHolder];
115+
size_t layer_id = [flutter_backing_store_data layerId];
116+
117+
CALayer* content_layer = ca_layer_map_[layer_id];
118+
119+
FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id;
120+
121+
content_layer.frame = content_layer.superlayer.bounds;
122+
content_layer.zPosition = layer_position;
123+
124+
// The surface is an OpenGL texture, which means it has origin in bottom left corner
125+
// and needs to be flipped vertically
126+
content_layer.transform = CATransform3DMakeScale(1, -1, 1);
127+
IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface];
128+
[content_layer setContents:(__bridge id)io_surface_contents];
129+
}
130+
112131
void FlutterGLCompositor::SetPresentCallback(
113132
const FlutterGLCompositor::PresentCallback& present_callback) {
114133
present_callback_ = present_callback;
@@ -139,4 +158,20 @@
139158
return ca_layer_count_++;
140159
}
141160

161+
void FlutterGLCompositor::DisposePlatformViews() {
162+
auto views_to_dispose = view_controller_.platformViewsToDispose;
163+
if (views_to_dispose.empty()) {
164+
return;
165+
}
166+
167+
for (int64_t viewId : views_to_dispose) {
168+
FML_CHECK([[NSThread currentThread] isMainThread])
169+
<< "Must be on the main thread to handle disposing platform views";
170+
NSView* view = view_controller_.platformViews[viewId];
171+
[view removeFromSuperview];
172+
view_controller_.platformViews.erase(viewId);
173+
}
174+
views_to_dispose.clear();
175+
}
176+
142177
} // namespace flutter
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERPLATFORMVIEWS_H_
6+
#define FLUTTER_FLUTTERPLATFORMVIEWS_H_
7+
8+
#import <AppKit/AppKit.h>
9+
10+
#import "FlutterCodecs.h"
11+
#import "FlutterMacros.h"
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
/**
16+
* Wraps a `NSView` for embedding in the Flutter hierarchy
17+
*/
18+
@protocol FlutterPlatformView <NSObject>
19+
/**
20+
* Returns a reference to the `NSView` that is wrapped by this `FlutterPlatformView`.
21+
*
22+
* It is recommended to return a cached view instance in this method.
23+
* Constructing and returning a new NSView instance in this method might cause undefined behavior.
24+
*
25+
* TODO(richardjcai): Prevent [FlutterPlatformView view] to be called multiple times
26+
* in a single frame.
27+
*/
28+
- (NSView*)view;
29+
@end
30+
31+
FLUTTER_EXPORT
32+
@protocol FlutterPlatformViewFactory <NSObject>
33+
/**
34+
* Create a `FlutterPlatformView`.
35+
*
36+
* Implemented by MacOS code that expose a `FlutterPlatformView` for embedding in a Flutter app.
37+
*
38+
* The implementation of this method should create a new `FlutterPlatformView` and return it.
39+
*
40+
* @param frame The rectangle for the newly created `FlutterPlatformView` measured in points.
41+
* @param viewId A unique identifier for this `FlutterPlatformView`.
42+
* @param args Parameters for creating the `FlutterPlatformView` sent from the Dart side of the
43+
* Flutter app. If `createArgsCodec` is not implemented, or if no creation arguments were sent from
44+
* the Dart code, this will be null. Otherwise this will be the value sent from the Dart code as
45+
* decoded by `createArgsCodec`.
46+
*/
47+
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
48+
viewIdentifier:(int64_t)viewId
49+
arguments:(id _Nullable)args;
50+
51+
/**
52+
* Returns the `FlutterMessageCodec` for decoding the args parameter of `createWithFrame`.
53+
*
54+
* Only needs to be implemented if `createWithFrame` needs an arguments parameter.
55+
*/
56+
@optional
57+
- (NSObject<FlutterMessageCodec>*)createArgsCodec;
58+
@end
59+
60+
NS_ASSUME_NONNULL_END
61+
62+
#endif // FLUTTER_FLUTTERPLATFORMVIEWS_H_

shell/platform/darwin/macos/framework/Source/FlutterViewController.mm

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ @implementation FlutterViewController {
198198

199199
// A method channel for miscellaneous platform functionality.
200200
FlutterMethodChannel* _platformChannel;
201+
202+
// A method channel for platform view functionality.
203+
FlutterMethodChannel* _platformViewsChannel;
201204
}
202205

203206
@dynamic view;
@@ -211,6 +214,7 @@ static void CommonInit(FlutterViewController* controller) {
211214
allowHeadlessExecution:NO];
212215
controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
213216
controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
217+
controller->_factories = [[NSMutableDictionary alloc] init];
214218
}
215219

216220
- (instancetype)initWithCoder:(NSCoder*)coder {
@@ -377,10 +381,70 @@ - (void)addInternalPlugins {
377381
[FlutterMethodChannel methodChannelWithName:@"flutter/platform"
378382
binaryMessenger:_engine.binaryMessenger
379383
codec:[FlutterJSONMethodCodec sharedInstance]];
384+
385+
_platformViewsChannel =
386+
[FlutterMethodChannel methodChannelWithName:@"flutter/platform_views"
387+
binaryMessenger:_engine.binaryMessenger
388+
codec:[FlutterStandardMethodCodec sharedInstance]];
389+
380390
__weak FlutterViewController* weakSelf = self;
381391
[_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
382392
[weakSelf handleMethodCall:call result:result];
383393
}];
394+
395+
[_platformViewsChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
396+
[weakSelf handleMethodCall:call result:result];
397+
}];
398+
}
399+
400+
- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result {
401+
NSMutableDictionary<NSString*, id>* args = [call arguments];
402+
int64_t viewId = [args[@"id"] longValue];
403+
NSString* viewType = [NSString stringWithUTF8String:([args[@"viewType"] UTF8String])];
404+
405+
if (_platformViews.count(viewId) != 0) {
406+
result([FlutterError errorWithCode:@"recreating_view"
407+
message:@"trying to create an already created view"
408+
details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
409+
}
410+
411+
NSObject<FlutterPlatformViewFactory>* factory = _factories[viewType];
412+
if (factory == nil) {
413+
result([FlutterError errorWithCode:@"unregistered_view_type"
414+
message:@"trying to create a view with an unregistered type"
415+
details:[NSString stringWithFormat:@"unregistered view type: '%@'",
416+
args[@"viewType"]]]);
417+
return;
418+
}
419+
420+
NSObject<FlutterPlatformView>* platform_view = [factory createWithFrame:CGRectZero
421+
viewIdentifier:viewId
422+
arguments:nil];
423+
424+
_platformViews[viewId] = [platform_view view];
425+
result(nil);
426+
}
427+
428+
- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result {
429+
NSNumber* arg = [call arguments];
430+
int64_t viewId = [arg longLongValue];
431+
NSLog(@"onDispose ViewId: %lld", viewId);
432+
433+
if (_platformViews.count(viewId) == 0) {
434+
result([FlutterError errorWithCode:@"unknown_view"
435+
message:@"trying to dispose an unknown"
436+
details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
437+
return;
438+
}
439+
440+
// The following FlutterGLCompositor::Present call will dispose the views.
441+
_platformViewsToDispose.insert(viewId);
442+
result(nil);
443+
}
444+
445+
- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory
446+
withId:(nonnull NSString*)factoryId {
447+
_factories[factoryId] = factory;
384448
}
385449

386450
- (void)dispatchMouseEvent:(nonnull NSEvent*)event {
@@ -511,6 +575,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
511575
result(nil);
512576
} else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
513577
result(@{@"value" : @([self clipboardHasStrings])});
578+
} else if ([[call method] isEqualToString:@"create"]) {
579+
[self onCreate:call result:result];
580+
} else if ([[call method] isEqualToString:@"dispose"]) {
581+
[self onDispose:call result:result];
514582
} else {
515583
result(FlutterMethodNotImplemented);
516584
}

shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,63 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
5+
#include <map>
6+
#include <unordered_set>
67

8+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
9+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViews.h"
710
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
811

912
@interface FlutterViewController ()
1013

1114
// The FlutterView for this view controller.
1215
@property(nonatomic, readonly, nullable) FlutterView* flutterView;
1316

17+
// NSDictionary maps strings to FlutterPlatformViewFactorys.
18+
@property(nonnull, nonatomic)
19+
NSMutableDictionary<NSString*, NSObject<FlutterPlatformViewFactory>*>* factories;
20+
21+
// A map of platform view ids to views.
22+
@property(nonatomic) std::map<int, NSView*> platformViews;
23+
24+
// View ids that are going to be disposed on the next present call.
25+
@property(nonatomic) std::unordered_set<int64_t> platformViewsToDispose;
26+
1427
/**
1528
* This just returns the NSPasteboard so that it can be mocked in the tests.
1629
*/
1730
@property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard;
1831

32+
/**
33+
* Platform View Methods.
34+
*/
35+
36+
/**
37+
* Creates a platform view using the arguments from the provided call.
38+
* The call's arguments should be castable to an NSMutableDictionary<NSString*, id>*
39+
* and the dictionary should at least hold one key for "id" that maps to the view id and
40+
* one key for "viewType" which maps to the view type (string) that was used to register
41+
* the factory.
42+
* FlutterResult is updated to contain nil for success or to contain
43+
* a FlutterError if there is an error.
44+
*/
45+
- (void)onCreate:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result;
46+
47+
/**
48+
* Disposes a platform view using the arguments from the provided call.
49+
* The call's arguments should be the Id (castable to NSNumber*) of the platform view
50+
* that should be disposed.
51+
* FlutterResult is updated to contain nil for success or a FlutterError if there is an error.
52+
*/
53+
- (void)onDispose:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result;
54+
55+
/**
56+
* Register a view factory by adding an entry into the factories_ map with key factoryId
57+
* and value factory.
58+
*/
59+
- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory
60+
withId:(nonnull NSString*)factoryId;
61+
1962
/**
2063
* Adds a responder for keyboard events. Key up and key down events are forwarded to all added
2164
* responders.

0 commit comments

Comments
 (0)