@@ -1773,17 +1773,15 @@ - (void)testClipRect {
17731773 [mockFlutterView setNeedsLayout ];
17741774 [mockFlutterView layoutIfNeeded ];
17751775
1776+ CGRect insideClipping = CGRectMake (2 , 2 , 3 , 3 );
17761777 for (int i = 0 ; i < 10 ; i++) {
17771778 for (int j = 0 ; j < 10 ; j++) {
17781779 CGPoint point = CGPointMake (i, j);
17791780 int alpha = [self alphaOfPoint: CGPointMake (i, j) onView: mockFlutterView];
1780- // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
1781- // are fully inside the clipped area.
1782- CGRect insideClipping = CGRectMake (3 , 3 , 1 , 1 );
17831781 if (CGRectContainsPoint (insideClipping, point)) {
17841782 XCTAssertEqual (alpha, 255 );
17851783 } else {
1786- XCTAssertLessThan (alpha, 255 );
1784+ XCTAssertEqual (alpha, 0 );
17871785 }
17881786 }
17891787 }
@@ -1848,17 +1846,42 @@ - (void)testClipRRect {
18481846 [mockFlutterView setNeedsLayout ];
18491847 [mockFlutterView layoutIfNeeded ];
18501848
1849+ /*
1850+ ClippingMask outterClipping
1851+ 2 3 4 5 6 7 2 3 4 5 6 7
1852+ 2 / - - - - \ 2 + - - - - +
1853+ 3 | | 3 | |
1854+ 4 | | 4 | |
1855+ 5 | | 5 | |
1856+ 6 | | 6 | |
1857+ 7 \ - - - - / 7 + - - - - +
1858+
1859+ innerClipping1 innerClipping2
1860+ 2 3 4 5 6 7 2 3 4 5 6 7
1861+ 2 + - - + 2
1862+ 3 | | 3 + - - - - +
1863+ 4 | | 4 | |
1864+ 5 | | 5 | |
1865+ 6 | | 6 + - - - - +
1866+ 7 + - - + 7
1867+ */
1868+ CGRect innerClipping1 = CGRectMake (3 , 2 , 4 , 6 );
1869+ CGRect innerClipping2 = CGRectMake (2 , 3 , 6 , 4 );
1870+ CGRect outterClipping = CGRectMake (2 , 2 , 6 , 6 );
18511871 for (int i = 0 ; i < 10 ; i++) {
18521872 for (int j = 0 ; j < 10 ; j++) {
18531873 CGPoint point = CGPointMake (i, j);
18541874 int alpha = [self alphaOfPoint: CGPointMake (i, j) onView: mockFlutterView];
1855- // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
1856- // are fully inside the clipped area.
1857- CGRect insideClipping = CGRectMake (3 , 3 , 4 , 4 );
1858- if (CGRectContainsPoint (insideClipping, point)) {
1875+ if (CGRectContainsPoint (innerClipping1, point) ||
1876+ CGRectContainsPoint (innerClipping2, point)) {
1877+ // Pixels inside either of the 2 inner clippings should be fully opaque.
18591878 XCTAssertEqual (alpha, 255 );
1879+ } else if (CGRectContainsPoint (outterClipping, point)) {
1880+ // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
1881+ XCTAssert (0 < alpha && alpha < 255 );
18601882 } else {
1861- XCTAssertLessThan (alpha, 255 );
1883+ // Pixels outside outterClipping should be fully transparent.
1884+ XCTAssertEqual (alpha, 0 );
18621885 }
18631886 }
18641887 }
@@ -1924,17 +1947,42 @@ - (void)testClipPath {
19241947 [mockFlutterView setNeedsLayout ];
19251948 [mockFlutterView layoutIfNeeded ];
19261949
1950+ /*
1951+ ClippingMask outterClipping
1952+ 2 3 4 5 6 7 2 3 4 5 6 7
1953+ 2 / - - - - \ 2 + - - - - +
1954+ 3 | | 3 | |
1955+ 4 | | 4 | |
1956+ 5 | | 5 | |
1957+ 6 | | 6 | |
1958+ 7 \ - - - - / 7 + - - - - +
1959+
1960+ innerClipping1 innerClipping2
1961+ 2 3 4 5 6 7 2 3 4 5 6 7
1962+ 2 + - - + 2
1963+ 3 | | 3 + - - - - +
1964+ 4 | | 4 | |
1965+ 5 | | 5 | |
1966+ 6 | | 6 + - - - - +
1967+ 7 + - - + 7
1968+ */
1969+ CGRect innerClipping1 = CGRectMake (3 , 2 , 4 , 6 );
1970+ CGRect innerClipping2 = CGRectMake (2 , 3 , 6 , 4 );
1971+ CGRect outterClipping = CGRectMake (2 , 2 , 6 , 6 );
19271972 for (int i = 0 ; i < 10 ; i++) {
19281973 for (int j = 0 ; j < 10 ; j++) {
19291974 CGPoint point = CGPointMake (i, j);
19301975 int alpha = [self alphaOfPoint: CGPointMake (i, j) onView: mockFlutterView];
1931- // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
1932- // are fully inside the clipped area.
1933- CGRect insideClipping = CGRectMake (3 , 3 , 4 , 4 );
1934- if (CGRectContainsPoint (insideClipping, point)) {
1976+ if (CGRectContainsPoint (innerClipping1, point) ||
1977+ CGRectContainsPoint (innerClipping2, point)) {
1978+ // Pixels inside either of the 2 inner clippings should be fully opaque.
19351979 XCTAssertEqual (alpha, 255 );
1980+ } else if (CGRectContainsPoint (outterClipping, point)) {
1981+ // Corner pixels (i.e. (2, 2), (2, 7), (7, 2) and (7, 7)) should be partially transparent.
1982+ XCTAssert (0 < alpha && alpha < 255 );
19361983 } else {
1937- XCTAssertLessThan (alpha, 255 );
1984+ // Pixels outside outterClipping should be fully transparent.
1985+ XCTAssertEqual (alpha, 0 );
19381986 }
19391987 }
19401988 }
@@ -2989,6 +3037,69 @@ - (void)testDifferentClipMaskViewIsUsedForEachView {
29893037 XCTAssertNotEqual (maskView1, maskView2);
29903038}
29913039
3040+ - (void )testMaskViewUsesCAShapeLayerAsTheBackingLayer {
3041+ flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
3042+ auto thread_task_runner = CreateNewThread (" FlutterPlatformViewsTest" );
3043+ flutter::TaskRunners runners (/* label=*/ self.name .UTF8String ,
3044+ /* platform=*/ thread_task_runner,
3045+ /* raster=*/ thread_task_runner,
3046+ /* ui=*/ thread_task_runner,
3047+ /* io=*/ thread_task_runner);
3048+ auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
3049+ auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
3050+ /* delegate=*/ mock_delegate,
3051+ /* rendering_api=*/ mock_delegate.settings_ .enable_impeller
3052+ ? flutter::IOSRenderingAPI::kMetal
3053+ : flutter::IOSRenderingAPI::kSoftware ,
3054+ /* platform_views_controller=*/ flutterPlatformViewsController,
3055+ /* task_runners=*/ runners,
3056+ /* worker_task_runner=*/ nil ,
3057+ /* is_gpu_disabled_jsync_switch=*/ std::make_shared<fml::SyncSwitch>());
3058+
3059+ FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
3060+ [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc ] init ];
3061+ flutterPlatformViewsController->RegisterViewFactory (
3062+ factory, @" MockFlutterPlatformView" ,
3063+ FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
3064+ FlutterResult result = ^(id result) {
3065+ };
3066+
3067+ flutterPlatformViewsController->OnMethodCall (
3068+ [FlutterMethodCall
3069+ methodCallWithMethodName: @" create"
3070+ arguments: @{@" id" : @1 , @" viewType" : @" MockFlutterPlatformView" }],
3071+ result);
3072+
3073+ XCTAssertNotNil (gMockPlatformView );
3074+ UIView* mockFlutterView = [[UIView alloc ] initWithFrame: CGRectMake (0 , 0 , 10 , 10 )];
3075+ flutterPlatformViewsController->SetFlutterView (mockFlutterView);
3076+ // Create embedded view params
3077+ flutter::MutatorsStack stack1;
3078+ // Layer tree always pushes a screen scale factor to the stack
3079+ SkMatrix screenScaleMatrix =
3080+ SkMatrix::Scale ([UIScreen mainScreen ].scale , [UIScreen mainScreen ].scale );
3081+ stack1.PushTransform (screenScaleMatrix);
3082+ // Push a clip rect
3083+ SkRect rect = SkRect::MakeXYWH (2 , 2 , 3 , 3 );
3084+ stack1.PushClipRect (rect);
3085+
3086+ auto embeddedViewParams1 = std::make_unique<flutter::EmbeddedViewParams>(
3087+ screenScaleMatrix, SkSize::Make (10 , 10 ), stack1);
3088+
3089+ flutter::MutatorsStack stack2;
3090+ stack2.PushClipRect (rect);
3091+ auto embeddedViewParams2 = std::make_unique<flutter::EmbeddedViewParams>(
3092+ screenScaleMatrix, SkSize::Make (10 , 10 ), stack2);
3093+
3094+ flutterPlatformViewsController->PrerollCompositeEmbeddedView (1 , std::move (embeddedViewParams1));
3095+ flutterPlatformViewsController->CompositeEmbeddedView (1 );
3096+ UIView* childClippingView = gMockPlatformView .superview .superview ;
3097+
3098+ UIView* maskView = childClippingView.maskView ;
3099+ XCTAssert ([maskView.layer isKindOfClass: [CAShapeLayer class ]],
3100+ @" Mask view must use CAShapeLayer as its backing layer." );
3101+ }
3102+
29923103// Return true if a correct visual effect view is found. It also implies all the validation in this
29933104// method passes.
29943105//
0 commit comments