Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,48 @@ FLUTTER_DARWIN_EXPORT
*/
- (void)onPreEngineRestart;

/**
* The contentView (FlutterView)'s background color is set to black during
* its instantiation.
*
* The containing layer's color can be set to the NSColor provided to this method.
*
* For example, the background may be set after the FlutterViewController
* is instantiated in MainFlutterWindow.swift in the Flutter project.
* ```swift
* import Cocoa
* import FlutterMacOS
*
* class MainFlutterWindow: NSWindow {
* override func awakeFromNib() {
* let flutterViewController = FlutterViewController.init()
*
* // The background color of the window and `FlutterViewController`
* // are retained separately.
* //
* // In this example, both the MainFlutterWindow and FlutterViewController's
* // FlutterView's backgroundColor are set to clear to achieve a fully
* // transparent effect.
* //
* // If the window's background color is not set, it will use the system
* // default.
* //
* // If the `FlutterView`'s color is not set via `FlutterViewController.setBackgroundColor`
* // it's default will be black.
* self.backgroundColor = NSColor.clear
* flutterViewController.backgroundColor = NSColor.clear
*
* let windowFrame = self.frame
* self.contentViewController = flutterViewController
* self.setFrame(windowFrame, display: true)
*
* RegisterGeneratedPlugins(registry: flutterViewController)
*
* super.awakeFromNib()
* }
* }
* ```
*/
@property(readwrite, nonatomic, nullable) NSColor* backgroundColor;

@end
61 changes: 61 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,67 @@ @interface FlutterEngine (Test)
EXPECT_TRUE(logs.find("Hello logging") != std::string::npos);
}

TEST_F(FlutterEngineTest, BackgroundIsBlack) {
// Launch the test entrypoint.
FlutterEngine* engine = GetFlutterEngine();
EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
EXPECT_TRUE(engine.running);

NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
[engine setViewController:viewController];

// Latch to ensure the entire layer tree has been generated and presented.
fml::AutoResetWaitableEvent latch;
AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
CALayer* rootLayer = engine.viewController.flutterView.layer;
EXPECT_TRUE(rootLayer.backgroundColor != nil);
if (rootLayer.backgroundColor != nil) {
NSColor* actualBackgroundColor =
[NSColor colorWithCGColor:rootLayer.backgroundColor];
EXPECT_EQ(actualBackgroundColor, [NSColor blackColor]);
}
latch.Signal();
}));
latch.Wait();
}

TEST_F(FlutterEngineTest, CanOverrideBackgroundColor) {
// Launch the test entrypoint.
FlutterEngine* engine = GetFlutterEngine();
EXPECT_TRUE([engine runWithEntrypoint:@"backgroundTest"]);
EXPECT_TRUE(engine.running);

NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
[viewController loadView];
viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
[engine setViewController:viewController];
viewController.flutterView.backgroundColor = [NSColor whiteColor];

// Latch to ensure the entire layer tree has been generated and presented.
fml::AutoResetWaitableEvent latch;
AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
CALayer* rootLayer = engine.viewController.flutterView.layer;
EXPECT_TRUE(rootLayer.backgroundColor != nil);
if (rootLayer.backgroundColor != nil) {
NSColor* actualBackgroundColor =
[NSColor colorWithCGColor:rootLayer.backgroundColor];
EXPECT_EQ(actualBackgroundColor, [NSColor whiteColor]);
}
latch.Signal();
}));
latch.Wait();
}

TEST_F(FlutterEngineTest, CanToggleAccessibility) {
FlutterEngine* engine = GetFlutterEngine();
// Capture the update callbacks before the embedder API initializes.
Expand Down
9 changes: 9 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterView.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,13 @@
*/
- (void)shutdown;

/**
* By default, the `FlutterSurfaceManager` creates two layers to manage Flutter
* content, the content layer and containing layer. To set the native background
* color, onto which the Flutter content is drawn, call this method with the
* NSColor which you would like to override the default, black background color
* with.
*/
- (void)setBackgroundColor:(nonnull NSColor*)color;

@end
6 changes: 6 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ - (instancetype)initWithMTLDevice:(id<MTLDevice>)device
self = [super initWithFrame:NSZeroRect];
if (self) {
[self setWantsLayer:YES];
[self setBackgroundColor:[NSColor blackColor]];
[self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawDuringViewResize];
_reshapeListener = reshapeListener;
_resizableBackingStoreProvider =
Expand All @@ -51,6 +52,7 @@ - (instancetype)initWithFrame:(NSRect)frame
self = [super initWithFrame:frame];
if (self) {
[self setWantsLayer:YES];
[self setBackgroundColor:[NSColor blackColor]];
_reshapeListener = reshapeListener;
_resizableBackingStoreProvider =
[[FlutterOpenGLResizableBackingStoreProvider alloc] initWithMainContext:mainContext
Expand Down Expand Up @@ -84,6 +86,10 @@ - (void)reshaped {
}];
}

- (void)setBackgroundColor:(NSColor*)color {
self.layer.backgroundColor = color.CGColor;
}

#pragma mark - NSView overrides

- (void)setFrameSize:(NSSize)newSize {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ void Reset() {
*/
@interface FlutterViewWrapper : NSView

- (void)setBackgroundColor:(NSColor*)color;
Copy link
Contributor Author

@a-wallen a-wallen Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't pretty, but the FlutterViewController's content view is a FlutterViewWrapper and not a FlutterView. AFAIK, this is the only way to route the user's intent to the FlutterView.

cc @chunhtai, do you think there's a better way to do this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FlutterViewController should have direct access to FlutterView?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FlutterViewController's view is set to a wrapped FlutterView

FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc] initWithFlutterView:flutterView];
self.view = wrapperView;

but the FlutterViewWrapper's FlutterView is private.

@implementation FlutterViewWrapper {
FlutterView* _flutterView;
}

I tried calling setBackgroundColor on self.view (FlutterView) from FlutterViewController by casting self.view to a FlutterView. Oddly enough there was no runtime error, but the call took no effect until I routed the call through FlutterViewWrapper.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the FlutterViewController should be able to access flutter view directly

@property(nonatomic, readonly, nullable) FlutterView* flutterView;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, every time I reference this flutterView and call setBackgroundColor on it, the background color is never set. The current patch is the only way that I've found how to do it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably PEBKAC


@end

/**
Expand Down Expand Up @@ -266,6 +268,10 @@ - (instancetype)initWithFlutterView:(FlutterView*)view {
return self;
}

- (void)setBackgroundColor:(NSColor*)color {
[_flutterView setBackgroundColor:color];
}

- (NSArray*)accessibilityChildren {
return @[ _flutterView ];
}
Expand Down Expand Up @@ -376,6 +382,9 @@ - (void)loadView {
}
flutterView = [[FlutterView alloc] initWithMainContext:mainContext reshapeListener:self];
}
if (_backgroundColor != nil) {
[flutterView setBackgroundColor:_backgroundColor];
}
FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc] initWithFlutterView:flutterView];
self.view = wrapperView;
_flutterView = flutterView;
Expand Down Expand Up @@ -418,6 +427,11 @@ - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
[self configureTrackingArea];
}

- (void)setBackgroundColor:(NSColor*)color {
_backgroundColor = color;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are going to cache it, you will also need to apply the background color during the loadView.

[_flutterView setBackgroundColor:_backgroundColor];
}

- (void)onPreEngineRestart {
[self initializeKeyboard];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ Picture _createSimplePicture() {
void nativeCallback() {
signalNativeTest();
}

@pragma('vm:entry-point')
void backgroundTest() {
PlatformDispatcher.instance.views.first.render(SceneBuilder().build());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbracken we decided the test should look like

Picture _createSimplePicture() {
  Paint paint = Paint();
  paint.color = Color(0xFFFF3A00);
  PictureRecorder baseRecorder = PictureRecorder();
  Canvas canvas = Canvas(baseRecorder);
  canvas.drawRect(Rect.fromLTRB(0.0, 0.0, 1000.0, 1000.0), paint);
  return baseRecorder.endRecording();
}

void main() async {
  window.render(SceneBuilder().build());
  // 1. should look black

  final SceneBuilder builder = SceneBuilder();
  builder.addPicture(Offset(1.0, 1.0), _createSimplePicture());
  builder.pushOffset(1.0, 2.0);
  builder.pop(); // offset
  window.render(builder.build());
  // 2. should have a red square

  window.render(SceneBuilder().build());
  // 3. should be black again.
}

but flutter/flutter#113785 is preventing step 3 from producing the correct output. Before we can add a test like the one above, flutter/flutter#113785 has to be fixed.

signalNativeTest(); // should look black
}