This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
Use a single mask view to clip platform view #20050
Merged
cyanglaz
merged 17 commits into
flutter:master
from
cyanglaz:platform_view_mutation_single_view
Aug 3, 2020
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
db33eae
use mask view to clip
31db69b
only set alpha channel
0158df3
use fml::CFRef to wrap CGPathRef
9785f97
cleanup
eab8257
Update shell/platform/darwin/ios/framework/Source/FlutterPlatformView…
c0627b6
Merge branch 'master' into platform_view_mutation_single_view
31d44e8
update golden for iphone 8
087b769
revert
7c2ae3a
update doc
3a703fd
Merge branch 'platform_view_mutation_single_view' of github.com:cyang…
1ad2449
revert goldens
cab73d9
make mask view the same frame as flutter view
9b59753
fix mask view orignal and clipping operations's matrix
ae69e2b
update iphone 8 golden
fa983ab
add unittsets
a1cd71a
formatting
4c102ce
add tests for clipping
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,13 +145,24 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { | |
| } | ||
|
|
||
| - (void)testChildClippingViewHitTests { | ||
| ChildClippingView* childCilppingView = | ||
| ChildClippingView* childClippingView = | ||
| [[[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)] autorelease]; | ||
| UIView* childView = [[[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)] autorelease]; | ||
| [childCilppingView addSubview:childView]; | ||
|
|
||
| XCTAssertFalse([childCilppingView pointInside:CGPointMake(50, 50) withEvent:nil]); | ||
| XCTAssertTrue([childCilppingView pointInside:CGPointMake(150, 150) withEvent:nil]); | ||
| [childClippingView addSubview:childView]; | ||
|
|
||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]); | ||
| XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]); | ||
|
|
||
| XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]); | ||
| XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]); | ||
| XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]); | ||
| XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]); | ||
| XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]); | ||
| } | ||
|
|
||
| - (void)testCompositePlatformView { | ||
|
|
@@ -266,16 +277,252 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { | |
| // The childclippingview's frame is set based on flow, but the platform view's frame is set based | ||
| // on quartz. Although they should be the same, but we should tolerate small floating point | ||
| // errors. | ||
| XCTAssertTrue(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x) < | ||
| kFloatCompareEpsilon); | ||
| XCTAssertTrue(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y) < | ||
| kFloatCompareEpsilon); | ||
| XCTAssertTrue(fabs(platformViewRectInFlutterView.size.width - | ||
| childClippingView.frame.size.width) < kFloatCompareEpsilon); | ||
| XCTAssertTrue(fabs(platformViewRectInFlutterView.size.height - | ||
| childClippingView.frame.size.height) < kFloatCompareEpsilon); | ||
| XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x), | ||
| kFloatCompareEpsilon); | ||
| XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y), | ||
| kFloatCompareEpsilon); | ||
| XCTAssertLessThan( | ||
| fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width), | ||
| kFloatCompareEpsilon); | ||
| XCTAssertLessThan( | ||
| fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height), | ||
| kFloatCompareEpsilon); | ||
|
|
||
| flutterPlatformViewsController->Reset(); | ||
| } | ||
|
|
||
| - (void)testClipRect { | ||
| flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; | ||
| auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest"); | ||
| flutter::TaskRunners runners(/*label=*/self.name.UTF8String, | ||
| /*platform=*/thread_task_runner, | ||
| /*raster=*/thread_task_runner, | ||
| /*ui=*/thread_task_runner, | ||
| /*io=*/thread_task_runner); | ||
| auto platform_view = std::make_unique<flutter::PlatformViewIOS>( | ||
| /*delegate=*/mock_delegate, | ||
| /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, | ||
| /*task_runners=*/runners); | ||
|
|
||
| auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>(); | ||
|
|
||
| FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = | ||
| [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; | ||
| flutterPlatformViewsController->RegisterViewFactory( | ||
| factory, @"MockFlutterPlatformView", | ||
| FlutterPlatformViewGestureRecognizersBlockingPolicyEager); | ||
| FlutterResult result = ^(id result) { | ||
| }; | ||
| flutterPlatformViewsController->OnMethodCall( | ||
| [FlutterMethodCall | ||
| methodCallWithMethodName:@"create" | ||
| arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], | ||
| result); | ||
|
|
||
| XCTAssertNotNil(gMockPlatformView); | ||
|
|
||
| UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease]; | ||
| flutterPlatformViewsController->SetFlutterView(mockFlutterView); | ||
| // Create embedded view params | ||
| flutter::MutatorsStack stack; | ||
| // Layer tree always pushes a screen scale factor to the stack | ||
| SkMatrix screenScaleMatrix = | ||
| SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); | ||
| stack.PushTransform(screenScaleMatrix); | ||
| // Push a clip rect | ||
| SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3); | ||
| stack.PushClipRect(rect); | ||
|
|
||
| auto embeddedViewParams = | ||
| std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack); | ||
|
|
||
| flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); | ||
| flutterPlatformViewsController->CompositeEmbeddedView(2); | ||
| gMockPlatformView.backgroundColor = UIColor.redColor; | ||
| XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); | ||
| ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; | ||
| [mockFlutterView addSubview:childClippingView]; | ||
|
|
||
| [mockFlutterView setNeedsLayout]; | ||
| [mockFlutterView layoutIfNeeded]; | ||
|
|
||
| for (int i = 0; i < 10; i++) { | ||
| for (int j = 0; j < 10; j++) { | ||
| CGPoint point = CGPointMake(i, j); | ||
| int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView]; | ||
| // Edges of the clipping might have a semi transparent pixel, we only check the pixels that | ||
| // are fully inside the clipped area. | ||
| CGRect insideClipping = CGRectMake(3, 3, 1, 1); | ||
| if (CGRectContainsPoint(insideClipping, point)) { | ||
| XCTAssertEqual(alpha, 255); | ||
| } else { | ||
| XCTAssertLessThan(alpha, 255); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there an exact value for the alpha? |
||
| } | ||
| } | ||
| } | ||
| flutterPlatformViewsController->Reset(); | ||
| } | ||
|
|
||
| - (void)testClipRRect { | ||
| flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; | ||
| auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest"); | ||
| flutter::TaskRunners runners(/*label=*/self.name.UTF8String, | ||
| /*platform=*/thread_task_runner, | ||
| /*raster=*/thread_task_runner, | ||
| /*ui=*/thread_task_runner, | ||
| /*io=*/thread_task_runner); | ||
| auto platform_view = std::make_unique<flutter::PlatformViewIOS>( | ||
| /*delegate=*/mock_delegate, | ||
| /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, | ||
| /*task_runners=*/runners); | ||
|
|
||
| auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>(); | ||
|
|
||
| FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = | ||
| [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; | ||
| flutterPlatformViewsController->RegisterViewFactory( | ||
| factory, @"MockFlutterPlatformView", | ||
| FlutterPlatformViewGestureRecognizersBlockingPolicyEager); | ||
| FlutterResult result = ^(id result) { | ||
| }; | ||
| flutterPlatformViewsController->OnMethodCall( | ||
| [FlutterMethodCall | ||
| methodCallWithMethodName:@"create" | ||
| arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], | ||
| result); | ||
|
|
||
| XCTAssertNotNil(gMockPlatformView); | ||
|
|
||
| UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease]; | ||
| flutterPlatformViewsController->SetFlutterView(mockFlutterView); | ||
| // Create embedded view params | ||
| flutter::MutatorsStack stack; | ||
| // Layer tree always pushes a screen scale factor to the stack | ||
| SkMatrix screenScaleMatrix = | ||
| SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); | ||
| stack.PushTransform(screenScaleMatrix); | ||
| // Push a clip rrect | ||
| SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); | ||
| stack.PushClipRRect(rrect); | ||
|
|
||
| auto embeddedViewParams = | ||
| std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack); | ||
|
|
||
| flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); | ||
| flutterPlatformViewsController->CompositeEmbeddedView(2); | ||
| gMockPlatformView.backgroundColor = UIColor.redColor; | ||
| XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); | ||
| ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; | ||
| [mockFlutterView addSubview:childClippingView]; | ||
|
|
||
| [mockFlutterView setNeedsLayout]; | ||
| [mockFlutterView layoutIfNeeded]; | ||
|
|
||
| for (int i = 0; i < 10; i++) { | ||
| for (int j = 0; j < 10; j++) { | ||
| CGPoint point = CGPointMake(i, j); | ||
| int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView]; | ||
| // Edges of the clipping might have a semi transparent pixel, we only check the pixels that | ||
| // are fully inside the clipped area. | ||
| CGRect insideClipping = CGRectMake(3, 3, 4, 4); | ||
| if (CGRectContainsPoint(insideClipping, point)) { | ||
| XCTAssertEqual(alpha, 255); | ||
| } else { | ||
| XCTAssertLessThan(alpha, 255); | ||
| } | ||
| } | ||
| } | ||
| flutterPlatformViewsController->Reset(); | ||
| } | ||
|
|
||
| - (void)testClipPath { | ||
| flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; | ||
| auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest"); | ||
| flutter::TaskRunners runners(/*label=*/self.name.UTF8String, | ||
| /*platform=*/thread_task_runner, | ||
| /*raster=*/thread_task_runner, | ||
| /*ui=*/thread_task_runner, | ||
| /*io=*/thread_task_runner); | ||
| auto platform_view = std::make_unique<flutter::PlatformViewIOS>( | ||
| /*delegate=*/mock_delegate, | ||
| /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, | ||
| /*task_runners=*/runners); | ||
|
|
||
| auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>(); | ||
|
|
||
| FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = | ||
| [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; | ||
| flutterPlatformViewsController->RegisterViewFactory( | ||
| factory, @"MockFlutterPlatformView", | ||
| FlutterPlatformViewGestureRecognizersBlockingPolicyEager); | ||
| FlutterResult result = ^(id result) { | ||
| }; | ||
| flutterPlatformViewsController->OnMethodCall( | ||
| [FlutterMethodCall | ||
| methodCallWithMethodName:@"create" | ||
| arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}], | ||
| result); | ||
|
|
||
| XCTAssertNotNil(gMockPlatformView); | ||
|
|
||
| UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease]; | ||
| flutterPlatformViewsController->SetFlutterView(mockFlutterView); | ||
| // Create embedded view params | ||
| flutter::MutatorsStack stack; | ||
| // Layer tree always pushes a screen scale factor to the stack | ||
| SkMatrix screenScaleMatrix = | ||
| SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); | ||
| stack.PushTransform(screenScaleMatrix); | ||
| // Push a clip path | ||
| SkPath path; | ||
| path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1); | ||
| stack.PushClipPath(path); | ||
|
|
||
| auto embeddedViewParams = | ||
| std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack); | ||
|
|
||
| flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); | ||
| flutterPlatformViewsController->CompositeEmbeddedView(2); | ||
| gMockPlatformView.backgroundColor = UIColor.redColor; | ||
| XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]); | ||
| ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; | ||
| [mockFlutterView addSubview:childClippingView]; | ||
|
|
||
| [mockFlutterView setNeedsLayout]; | ||
| [mockFlutterView layoutIfNeeded]; | ||
|
|
||
| for (int i = 0; i < 10; i++) { | ||
| for (int j = 0; j < 10; j++) { | ||
| CGPoint point = CGPointMake(i, j); | ||
| int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView]; | ||
| // Edges of the clipping might have a semi transparent pixel, we only check the pixels that | ||
| // are fully inside the clipped area. | ||
| CGRect insideClipping = CGRectMake(3, 3, 4, 4); | ||
| if (CGRectContainsPoint(insideClipping, point)) { | ||
| XCTAssertEqual(alpha, 255); | ||
| } else { | ||
| XCTAssertLessThan(alpha, 255); | ||
| } | ||
| } | ||
| } | ||
| flutterPlatformViewsController->Reset(); | ||
| } | ||
|
|
||
| - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view { | ||
| unsigned char pixel[4] = {0}; | ||
|
|
||
| CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); | ||
|
|
||
| // Draw the pixel on `point` in the context. | ||
| CGContextRef context = CGBitmapContextCreate( | ||
| pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast); | ||
| CGContextTranslateCTM(context, -point.x, -point.y); | ||
| [view.layer renderInContext:context]; | ||
|
|
||
| CGContextRelease(context); | ||
| CGColorSpaceRelease(colorSpace); | ||
| // Get the alpha from the pixel that we just rendered. | ||
| return pixel[3]; | ||
| } | ||
|
|
||
| @end | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: does it need this to test clip specifically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. as the clip paths will need to get scaled as well.