Skip to content

Commit 5883356

Browse files
authored
[macOS] Top-left origin for PlatformView container (flutter#42523)
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: flutter#124490 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 8f7dc77 commit 5883356

2 files changed

Lines changed: 65 additions & 7 deletions

File tree

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

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
#include "flutter/shell/platform/embedder/embedder.h"
1313

1414
@interface FlutterMutatorView () {
15-
/// Each of these views clips to a CGPathRef. These views, if present,
16-
/// are nested (first is child of FlutterMutatorView and last is parent of
15+
// Each of these views clips to a CGPathRef. These views, if present,
16+
// are nested (first is child of FlutterMutatorView and last is parent of
1717
// _platformView).
1818
NSMutableArray* _pathClipViews;
1919

@@ -26,6 +26,21 @@ @interface FlutterMutatorView () {
2626

2727
@end
2828

29+
/// Superview container for platform views, to which sublayer transforms are applied.
30+
@interface FlutterPlatformViewContainer : NSView
31+
@end
32+
33+
@implementation FlutterPlatformViewContainer
34+
35+
- (BOOL)isFlipped {
36+
// Flutter transforms assume a coordinate system with an upper-left corner origin, with y
37+
// coordinate values increasing downwards. This affects the view, view transforms, and
38+
// sublayerTransforms.
39+
return YES;
40+
}
41+
42+
@end
43+
2944
/// View that clips that content to a specific CGPathRef.
3045
/// Clipping is done through a CAShapeLayer mask, which avoids the need to
3146
/// rasterize the mask.
@@ -43,6 +58,9 @@ - (instancetype)initWithFrame:(NSRect)frameRect {
4358
}
4459

4560
- (BOOL)isFlipped {
61+
// Flutter transforms assume a coordinate system with an upper-left corner origin, with y
62+
// coordinate values increasing downwards. This affects the view, view transforms, and
63+
// sublayerTransforms.
4664
return YES;
4765
}
4866

@@ -400,7 +418,7 @@ - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds
400418
clipRect:(CGRect)clipRect {
401419
// Create the PlatformViewContainer view if necessary.
402420
if (_platformViewContainer == nil) {
403-
_platformViewContainer = [[NSView alloc] initWithFrame:self.bounds];
421+
_platformViewContainer = [[FlutterPlatformViewContainer alloc] initWithFrame:self.bounds];
404422
_platformViewContainer.wantsLayer = YES;
405423
}
406424

@@ -409,14 +427,15 @@ - (void)updatePlatformViewWithBounds:(CGRect)untransformedBounds
409427
[containerSuperview addSubview:_platformViewContainer];
410428
_platformViewContainer.frame = self.bounds;
411429

412-
// Add the
430+
// Nest the platform view in the PlatformViewContainer.
413431
[_platformViewContainer addSubview:_platformView];
414432
_platformView.frame = untransformedBounds;
415433

416434
// Transform for the platform view is finalTransform adjusted for bounding rect origin.
417-
_platformViewContainer.layer.sublayerTransform =
418-
CATransform3DTranslate(transform, -transformedBounds.origin.x / transform.m11 /* scaleX */,
419-
-transformedBounds.origin.y / transform.m22 /* scaleY */, 0);
435+
CATransform3D translation =
436+
CATransform3DMakeTranslation(-transformedBounds.origin.x, -transformedBounds.origin.y, 0);
437+
transform = CATransform3DConcat(transform, translation);
438+
_platformViewContainer.layer.sublayerTransform = transform;
420439

421440
// By default NSView clips children to frame. If masterClip is tighter than mutator view frame,
422441
// the frame is set to masterClip and child offset adjusted to compensate for the difference.

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,45 @@ void ExpectTransform3DEqual(const CATransform3D& t, const CATransform3D& u) {
301301
EXPECT_EQ(mutatorView.pathClipViews.count, 0ul);
302302
}
303303

304+
// Ensure that the mutator view, clip views, and container all use a flipped y axis. The transforms
305+
// sent from the framework assume this, and so aside from the consistency with every other embedder,
306+
// we can avoid a lot of extra math.
307+
TEST(FlutterMutatorViewTest, ViewsSetIsFlipped) {
308+
NSView* platformView = [[NSView alloc] init];
309+
FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
310+
311+
std::vector<FlutterPlatformViewMutation> mutations{
312+
{
313+
.type = kFlutterPlatformViewMutationTypeClipRoundedRect,
314+
.clip_rounded_rect =
315+
FlutterRoundedRect{
316+
.rect = FlutterRect{110, 60, 150, 150},
317+
.upper_left_corner_radius = FlutterSize{10, 10},
318+
.upper_right_corner_radius = FlutterSize{10, 10},
319+
.lower_right_corner_radius = FlutterSize{10, 10},
320+
.lower_left_corner_radius = FlutterSize{10, 10},
321+
},
322+
},
323+
{
324+
.type = kFlutterPlatformViewMutationTypeTransformation,
325+
.transformation =
326+
FlutterTransformation{
327+
.scaleX = 1,
328+
.transX = 100,
329+
.scaleY = 1,
330+
.transY = 50,
331+
},
332+
},
333+
};
334+
335+
ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
336+
337+
EXPECT_TRUE(mutatorView.isFlipped);
338+
ASSERT_EQ(mutatorView.pathClipViews.count, 1ul);
339+
EXPECT_TRUE(mutatorView.pathClipViews.firstObject.isFlipped);
340+
EXPECT_TRUE(mutatorView.platformViewContainer.isFlipped);
341+
}
342+
304343
TEST(FlutterMutatorViewTest, RoundRectClipsToPath) {
305344
NSView* platformView = [[NSView alloc] init];
306345
FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];

0 commit comments

Comments
 (0)