From c5e38598818e34db7cf6b3b3abb32930aa4089f6 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 30 May 2023 15:09:47 -0700 Subject: [PATCH 1/2] [macOS] Top-left origin for PlatformView container For consistency with Flutter (and all other platforms), Flutter views in the macOS embedder set `isFlipped` to ensure a co-ordinate system with the origin in the top-left, with y co-ordinates increasing towards the bottom edge of the view. Previously, we were using a stock NSView as the container, which uses a bottom-left origin by default. Instead we extract the PlatformView container view as its own class with `isFlipped` true. This doesn't correct the issue of the view anchorpoint/position but does correct rotation direction. This also applies the transform back to origin prior to other transforms when adjusting the platformview position rather than after. Issue: https://github.com/flutter/flutter/issues/124490 --- .../framework/Source/FlutterMutatorView.mm | 31 +++++++++++---- .../Source/FlutterMutatorViewTest.mm | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm b/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm index de10cd468071d..8ae8cdf32d899 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,20 @@ @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. + 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 +57,8 @@ - (instancetype)initWithFrame:(NSRect)frameRect { } - (BOOL)isFlipped { + // Flutter transforms assume a coordinate system with an upper-left corner origin, with y + // coordinate values increasing downwards. return YES; } @@ -400,7 +416,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 +425,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]; From 4e9425c0f01888de4ea35318812510b1aae60fbe Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 2 Jun 2023 15:21:54 -0700 Subject: [PATCH 2/2] Mention transforms, sublayer transforms. --- .../darwin/macos/framework/Source/FlutterMutatorView.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm b/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm index 8ae8cdf32d899..e0cf184d0c475 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm @@ -34,7 +34,8 @@ @implementation FlutterPlatformViewContainer - (BOOL)isFlipped { // Flutter transforms assume a coordinate system with an upper-left corner origin, with y - // coordinate values increasing downwards. + // coordinate values increasing downwards. This affects the view, view transforms, and + // sublayerTransforms. return YES; } @@ -58,7 +59,8 @@ - (instancetype)initWithFrame:(NSRect)frameRect { - (BOOL)isFlipped { // Flutter transforms assume a coordinate system with an upper-left corner origin, with y - // coordinate values increasing downwards. + // coordinate values increasing downwards. This affects the view, view transforms, and + // sublayerTransforms. return YES; }