diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm b/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm index de10cd468071d..e0cf184d0c475 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm @@ -12,8 +12,8 @@ #include "flutter/shell/platform/embedder/embedder.h" @interface FlutterMutatorView () { - /// Each of these views clips to a CGPathRef. These views, if present, - /// are nested (first is child of FlutterMutatorView and last is parent of + // Each of these views clips to a CGPathRef. These views, if present, + // are nested (first is child of FlutterMutatorView and last is parent of // _platformView). NSMutableArray* _pathClipViews; @@ -26,6 +26,21 @@ @interface FlutterMutatorView () { @end +/// Superview container for platform views, to which sublayer transforms are applied. +@interface FlutterPlatformViewContainer : NSView +@end + +@implementation FlutterPlatformViewContainer + +- (BOOL)isFlipped { + // Flutter transforms assume a coordinate system with an upper-left corner origin, with y + // coordinate values increasing downwards. This affects the view, view transforms, and + // sublayerTransforms. + return YES; +} + +@end + /// View that clips that content to a specific CGPathRef. /// Clipping is done through a CAShapeLayer mask, which avoids the need to /// rasterize the mask. @@ -43,6 +58,9 @@ - (instancetype)initWithFrame:(NSRect)frameRect { } - (BOOL)isFlipped { + // Flutter transforms assume a coordinate system with an upper-left corner origin, with y + // coordinate values increasing downwards. This affects the view, view transforms, and + // sublayerTransforms. return YES; } @@ -400,7 +418,7 @@ - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds clipRect:(CGRect)clipRect { // Create the PlatformViewContainer view if necessary. if (_platformViewContainer == nil) { - _platformViewContainer = [[NSView alloc] initWithFrame:self.bounds]; + _platformViewContainer = [[FlutterPlatformViewContainer alloc] initWithFrame:self.bounds]; _platformViewContainer.wantsLayer = YES; } @@ -409,14 +427,15 @@ - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds [containerSuperview addSubview:_platformViewContainer]; _platformViewContainer.frame = self.bounds; - // Add the + // Nest the platform view in the PlatformViewContainer. [_platformViewContainer addSubview:_platformView]; _platformView.frame = untransformedBounds; // Transform for the platform view is finalTransform adjusted for bounding rect origin. - _platformViewContainer.layer.sublayerTransform = - CATransform3DTranslate(transform, -transformedBounds.origin.x / transform.m11 /* scaleX */, - -transformedBounds.origin.y / transform.m22 /* scaleY */, 0); + CATransform3D translation = + CATransform3DMakeTranslation(-transformedBounds.origin.x, -transformedBounds.origin.y, 0); + transform = CATransform3DConcat(transform, translation); + _platformViewContainer.layer.sublayerTransform = transform; // By default NSView clips children to frame. If masterClip is tighter than mutator view frame, // the frame is set to masterClip and child offset adjusted to compensate for the difference. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm index 78a59a9213940..f9e7bb6f3092e 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm @@ -301,6 +301,45 @@ void ExpectTransform3DEqual(const CATransform3D& t, const CATransform3D& u) { EXPECT_EQ(mutatorView.pathClipViews.count, 0ul); } +// Ensure that the mutator view, clip views, and container all use a flipped y axis. The transforms +// sent from the framework assume this, and so aside from the consistency with every other embedder, +// we can avoid a lot of extra math. +TEST(FlutterMutatorViewTest, ViewsSetIsFlipped) { + NSView* platformView = [[NSView alloc] init]; + FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView]; + + std::vector mutations{ + { + .type = kFlutterPlatformViewMutationTypeClipRoundedRect, + .clip_rounded_rect = + FlutterRoundedRect{ + .rect = FlutterRect{110, 60, 150, 150}, + .upper_left_corner_radius = FlutterSize{10, 10}, + .upper_right_corner_radius = FlutterSize{10, 10}, + .lower_right_corner_radius = FlutterSize{10, 10}, + .lower_left_corner_radius = FlutterSize{10, 10}, + }, + }, + { + .type = kFlutterPlatformViewMutationTypeTransformation, + .transformation = + FlutterTransformation{ + .scaleX = 1, + .transX = 100, + .scaleY = 1, + .transY = 50, + }, + }, + }; + + ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations); + + EXPECT_TRUE(mutatorView.isFlipped); + ASSERT_EQ(mutatorView.pathClipViews.count, 1ul); + EXPECT_TRUE(mutatorView.pathClipViews.firstObject.isFlipped); + EXPECT_TRUE(mutatorView.platformViewContainer.isFlipped); +} + TEST(FlutterMutatorViewTest, RoundRectClipsToPath) { NSView* platformView = [[NSView alloc] init]; FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];