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

Commit f2c3d78

Browse files
author
Chris Yang
committed
only clip when necessary
tests fix tests format review fix
1 parent cae1716 commit f2c3d78

File tree

4 files changed

+156
-31
lines changed

4 files changed

+156
-31
lines changed

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
3232
}
3333
@end
3434

35+
// Determines if the final `clipBounds` from a clipRect/clipRRect/clipPath mutator contains the
36+
// `platformview_boundingrect`.
37+
//
38+
// `clip_bounds` is the bounding rect of the rect/rrect/path in the clipRect/clipRRect/clipPath
39+
// mutator. This rect is in its own coordinate space. The rect needs to be transformed by
40+
// `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
41+
//
42+
// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
43+
// space where the PlatformView is displayed.
44+
static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds,
45+
const SkRect& platformview_boundingrect,
46+
const SkMatrix& transform_matrix) {
47+
SkRect transforme_clip_bounds = transform_matrix.mapRect(clip_bounds);
48+
return transforme_clip_bounds.contains(platformview_boundingrect);
49+
}
50+
3551
namespace flutter {
3652
// Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
3753
BOOL canApplyBlurBackdrop = YES;
@@ -404,47 +420,62 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
404420
}
405421

406422
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
407-
UIView* embedded_view) {
423+
UIView* embedded_view,
424+
const SkRect& bounding_rect) {
408425
if (flutter_view_ == nullptr) {
409426
return;
410427
}
411428
FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
412429
ResetAnchor(embedded_view.layer);
413430
ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
414431

415-
// The UIKit frame is set based on the logical resolution instead of physical.
416-
// (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
417-
// However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
418-
// 500 points in UIKit. And until this point, we did all the calculation based on the flow
419-
// resolution. So we need to scale down to match UIKit's logical resolution.
420432
CGFloat screenScale = [UIScreen mainScreen].scale;
421-
CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
422433

423434
UIView* flutter_view = flutter_view_.get();
424435
FlutterClippingMaskView* maskView = [[[FlutterClippingMaskView alloc]
425436
initWithFrame:CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
426437
CGRectGetWidth(flutter_view.bounds),
427-
CGRectGetHeight(flutter_view.bounds))] autorelease];
438+
CGRectGetHeight(flutter_view.bounds))
439+
screenScale:screenScale] autorelease];
428440

441+
SkMatrix transformMatrix;
429442
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
430443

444+
clipView.maskView = nil;
431445
auto iter = mutators_stack.Begin();
432446
while (iter != mutators_stack.End()) {
433447
switch ((*iter)->GetType()) {
434448
case kTransform: {
435-
CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
436-
finalTransform = CATransform3DConcat(transform, finalTransform);
449+
transformMatrix.preConcat((*iter)->GetMatrix());
437450
break;
438451
}
439-
case kClipRect:
440-
[maskView clipRect:(*iter)->GetRect() matrix:finalTransform];
452+
case kClipRect: {
453+
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect,
454+
transformMatrix)) {
455+
break;
456+
}
457+
[maskView clipRect:(*iter)->GetRect() matrix:transformMatrix];
458+
clipView.maskView = maskView;
441459
break;
442-
case kClipRRect:
443-
[maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform];
460+
}
461+
case kClipRRect: {
462+
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetRRect().getBounds(),
463+
bounding_rect, transformMatrix)) {
464+
break;
465+
}
466+
[maskView clipRRect:(*iter)->GetRRect() matrix:transformMatrix];
467+
clipView.maskView = maskView;
444468
break;
445-
case kClipPath:
446-
[maskView clipPath:(*iter)->GetPath() matrix:finalTransform];
469+
}
470+
case kClipPath: {
471+
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetPath().getBounds(),
472+
bounding_rect, transformMatrix)) {
473+
break;
474+
}
475+
[maskView clipPath:(*iter)->GetPath() matrix:transformMatrix];
476+
clipView.maskView = maskView;
447477
break;
478+
}
448479
case kOpacity:
449480
embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
450481
break;
@@ -489,17 +520,23 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
489520
[clipView applyBlurBackdropFilters:blurFilters];
490521
}
491522

523+
// The UIKit frame is set based on the logical resolution instead of physical.
524+
// (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
525+
// However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
526+
// 500 points in UIKit. And until this point, we did all the calculation based on the flow
527+
// resolution. So we need to scale down to match UIKit's logical resolution.
528+
transformMatrix.postScale(1 / screenScale, 1 / screenScale);
529+
492530
// Reverse the offset of the clipView.
493531
// The clipView's frame includes the final translate of the final transform matrix.
494532
// Thus, this translate needs to be reversed so the platform view can layout at the correct
495533
// offset.
496534
//
497535
// Note that the transforms are not applied to the clipping paths because clipping paths happen on
498536
// the mask view, whose origin is always (0,0) to the flutter_view.
499-
CATransform3D reverseTranslate =
500-
CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0);
501-
embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate);
502-
clipView.maskView = maskView;
537+
transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y);
538+
539+
embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
503540
}
504541

505542
void FlutterPlatformViewsController::CompositeWithParams(int view_id,
@@ -538,7 +575,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
538575
CGFloat screenScale = [UIScreen mainScreen].scale;
539576
clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
540577
rect.width() / screenScale, rect.height() / screenScale);
541-
ApplyMutators(mutatorStack, touchInterceptor);
578+
ApplyMutators(mutatorStack, touchInterceptor, rect);
542579
}
543580

544581
EmbedderPaintContext FlutterPlatformViewsController::CompositeEmbeddedView(int view_id) {

shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,74 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
14781478
kFloatCompareEpsilon);
14791479
}
14801480

1481+
- (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
1482+
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
1483+
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
1484+
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1485+
/*platform=*/thread_task_runner,
1486+
/*raster=*/thread_task_runner,
1487+
/*ui=*/thread_task_runner,
1488+
/*io=*/thread_task_runner);
1489+
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
1490+
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1491+
/*delegate=*/mock_delegate,
1492+
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
1493+
/*platform_views_controller=*/flutterPlatformViewsController,
1494+
/*task_runners=*/runners);
1495+
1496+
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
1497+
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
1498+
flutterPlatformViewsController->RegisterViewFactory(
1499+
factory, @"MockFlutterPlatformView",
1500+
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
1501+
FlutterResult result = ^(id result) {
1502+
};
1503+
flutterPlatformViewsController->OnMethodCall(
1504+
[FlutterMethodCall
1505+
methodCallWithMethodName:@"create"
1506+
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
1507+
result);
1508+
1509+
XCTAssertNotNil(gMockPlatformView);
1510+
1511+
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)] autorelease];
1512+
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
1513+
// Create embedded view params
1514+
flutter::MutatorsStack stack;
1515+
// Layer tree always pushes a screen scale factor to the stack
1516+
SkMatrix screenScaleMatrix =
1517+
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
1518+
stack.PushTransform(screenScaleMatrix);
1519+
SkMatrix translateMatrix = SkMatrix::Translate(5, 5);
1520+
// The platform view's rect for this test will be (5, 5, 10, 10)
1521+
stack.PushTransform(translateMatrix);
1522+
// Push a clip rect, big enough to contain the entire platform view bound
1523+
SkRect rect = SkRect::MakeXYWH(0, 0, 25, 25);
1524+
stack.PushClipRect(rect);
1525+
// Push a clip rrect, big enough to contain the entire platform view bound
1526+
SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 24, 24);
1527+
SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1);
1528+
stack.PushClipRRect(rrect);
1529+
// Push a clip path, big enough to contain the entire platform view bound
1530+
SkPath path = SkPath::RRect(SkRect::MakeXYWH(0, 0, 23, 23), 1, 1);
1531+
stack.PushClipPath(path);
1532+
1533+
auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
1534+
SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack);
1535+
1536+
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
1537+
flutterPlatformViewsController->CompositeEmbeddedView(2);
1538+
gMockPlatformView.backgroundColor = UIColor.redColor;
1539+
XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
1540+
ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
1541+
[mockFlutterView addSubview:childClippingView];
1542+
1543+
[mockFlutterView setNeedsLayout];
1544+
[mockFlutterView layoutIfNeeded];
1545+
1546+
XCTAssertNil(childClippingView.maskView);
1547+
}
1548+
14811549
- (void)testClipRect {
14821550
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
14831551
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,22 @@
2828
// is replaced with the alpha channel of the |FlutterClippingMaskView|.
2929
@interface FlutterClippingMaskView : UIView
3030

31+
- (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale;
32+
3133
// Adds a clip rect operation to the queue.
3234
//
3335
// The `clipSkRect` is transformed with the `matrix` before adding to the queue.
34-
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix;
36+
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const SkMatrix&)matrix;
3537

3638
// Adds a clip rrect operation to the queue.
3739
//
3840
// The `clipSkRRect` is transformed with the `matrix` before adding to the queue.
39-
- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matrix;
41+
- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix;
4042

4143
// Adds a clip path operation to the queue.
4244
//
4345
// The `path` is transformed with the `matrix` before adding to the queue.
44-
- (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix;
46+
- (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix;
4547

4648
@end
4749

@@ -280,7 +282,9 @@ class FlutterPlatformViewsController {
280282
// T_1 is applied to C_2, T_3 and T_4 are applied to C_5, and T_6 is applied to PLATFORM_VIEW.
281283
//
282284
// After each clip operation, we update the head to the super view of the current head.
283-
void ApplyMutators(const MutatorsStack& mutators_stack, UIView* embedded_view);
285+
void ApplyMutators(const MutatorsStack& mutators_stack,
286+
UIView* embedded_view,
287+
const SkRect& bounding_rect);
284288
void CompositeWithParams(int view_id, const EmbeddedViewParams& params);
285289

286290
// Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from

shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ - (NSMutableArray*)backdropFilterSubviews {
241241

242242
@interface FlutterClippingMaskView ()
243243

244+
@property(nonatomic) CATransform3D reverseScreenScale;
245+
244246
- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
245247

246248
@end
@@ -250,8 +252,13 @@ @implementation FlutterClippingMaskView {
250252
}
251253

252254
- (instancetype)initWithFrame:(CGRect)frame {
255+
return [self initWithFrame:frame screenScale:[UIScreen mainScreen].scale];
256+
}
257+
258+
- (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale {
253259
if (self = [super initWithFrame:frame]) {
254260
self.backgroundColor = UIColor.clearColor;
261+
_reverseScreenScale = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
255262
}
256263
return self;
257264
}
@@ -280,13 +287,16 @@ - (void)drawRect:(CGRect)rect {
280287
CGContextRestoreGState(context);
281288
}
282289

283-
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix {
290+
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const SkMatrix&)matrix {
284291
CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect);
285292
CGPathRef path = CGPathCreateWithRect(clipRect, nil);
286-
paths_.push_back([self getTransformedPath:path matrix:matrix]);
293+
// The `matrix` is based on the physical pixels, convert it to UIKit points.
294+
CATransform3D reverseScaledMatrix =
295+
CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale);
296+
paths_.push_back([self getTransformedPath:path matrix:reverseScaledMatrix]);
287297
}
288298

289-
- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matrix {
299+
- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix {
290300
CGPathRef pathRef = nullptr;
291301
switch (clipSkRRect.getType()) {
292302
case SkRRect::kEmpty_Type: {
@@ -346,13 +356,16 @@ - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matri
346356
break;
347357
}
348358
}
359+
// The `matrix` is based on the physical pixels, convert it to UIKit points.
360+
CATransform3D reverseScaledMatrix =
361+
CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale);
349362
// TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that
350363
// the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge
351364
// clipping on iOS.
352-
paths_.push_back([self getTransformedPath:pathRef matrix:matrix]);
365+
paths_.push_back([self getTransformedPath:pathRef matrix:reverseScaledMatrix]);
353366
}
354367

355-
- (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix {
368+
- (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix {
356369
if (!path.isValid()) {
357370
return;
358371
}
@@ -411,7 +424,10 @@ - (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix {
411424
}
412425
verb = iter.next(pts);
413426
}
414-
paths_.push_back([self getTransformedPath:pathRef matrix:matrix]);
427+
// The `matrix` is based on the physical pixels, convert it to UIKit points.
428+
CATransform3D reverseScaledMatrix =
429+
CATransform3DConcat(flutter::GetCATransform3DFromSkMatrix(matrix), _reverseScreenScale);
430+
paths_.push_back([self getTransformedPath:pathRef matrix:reverseScaledMatrix]);
415431
}
416432

417433
- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {

0 commit comments

Comments
 (0)