Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
draft
fix

rename

unittest
  • Loading branch information
Chris Yang committed Jan 18, 2023
commit bd92e72cdeb389773aa6588451a18865a00c3924
63 changes: 47 additions & 16 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,52 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
}
@end

// Determines if the final `clipBounds` from a clipRect/clipRRect/clipPath mutator contains the
// Determines if the `clip_rect` from a clipRect mutator contains the
// `platformview_boundingrect`.
//
// `clip_bounds` is the bounding rect of the rect/rrect/path in the clipRect/clipRRect/clipPath
// mutator. This rect is in its own coordinate space. The rect needs to be transformed by
// `clip_rect` is in its own coordinate space. The rect needs to be transformed by
// `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
//
// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
// space where the PlatformView is displayed.
static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds,
const SkRect& platformview_boundingrect,
const SkMatrix& transform_matrix) {
SkRect transforme_clip_bounds = transform_matrix.mapRect(clip_bounds);
return transforme_clip_bounds.contains(platformview_boundingrect);
static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect,
const SkRect& platformview_boundingrect,
const SkMatrix& transform_matrix) {
SkRect transformed_rect = transform_matrix.mapRect(clip_rect);
return transformed_rect.contains(platformview_boundingrect);
}

// Determines if the `clipRRect` from a clipRRect mutator contains the
// `platformview_boundingrect`.
//
// `clip_rrect` is in its own coordinate space. The rrect needs to be transformed by
// `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
//
// `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
// space where the PlatformView is displayed.
static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
const SkRect& platformview_boundingrect,
const SkMatrix& transform_matrix) {
SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner);
SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner);
SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner);
SkVector lower_left = clip_rrect.radii(SkRRect::Corner::kLowerLeft_Corner);
SkScalar transformed_upper_left_x = transform_matrix.mapRadius(upper_left.x());
SkScalar transformed_upper_left_y = transform_matrix.mapRadius(upper_left.y());
SkScalar transformed_upper_right_x = transform_matrix.mapRadius(upper_right.x());
SkScalar transformed_upper_right_y = transform_matrix.mapRadius(upper_right.y());
SkScalar transformed_lower_right_x = transform_matrix.mapRadius(lower_right.x());
SkScalar transformed_lower_right_y = transform_matrix.mapRadius(lower_right.y());
SkScalar transformed_lower_left_x = transform_matrix.mapRadius(lower_left.x());
SkScalar transformed_lower_left_y = transform_matrix.mapRadius(lower_left.y());
SkRect transformed_clip_rect = transform_matrix.mapRect(clip_rrect.rect());
SkRRect transformed_rrect;
SkVector corners[] = {{transformed_upper_left_x, transformed_upper_left_y},
{transformed_upper_right_x, transformed_upper_right_y},
{transformed_lower_right_x, transformed_lower_right_y},
{transformed_lower_left_x, transformed_lower_left_y}};
transformed_rrect.setRectRadii(transformed_clip_rect, corners);
return transformed_rrect.contains(platformview_boundingrect);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assume this contains actually take its radii into consideration (rather than just checking rectangular bounds)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it does. the new scenario app test covers it. The new unit test also covered it. I had to use (-1, -1) as the origin for the clipRRect with a 1 radius to avoid clipping the PlatformView at (0, 0) origin.

}

namespace flutter {
Expand Down Expand Up @@ -450,28 +482,27 @@ static bool ClipBoundsContainsPlatformViewBoundingRect(const SkRect& clip_bounds
break;
}
case kClipRect: {
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect,
transformMatrix)) {
if (ClipRectContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect,
transformMatrix)) {
break;
}
[maskView clipRect:(*iter)->GetRect() matrix:transformMatrix];
clipView.maskView = maskView;
break;
}
case kClipRRect: {
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetRRect().getBounds(),
bounding_rect, transformMatrix)) {
if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), bounding_rect,
transformMatrix)) {
break;
}
[maskView clipRRect:(*iter)->GetRRect() matrix:transformMatrix];
clipView.maskView = maskView;
break;
}
case kClipPath: {
if (ClipBoundsContainsPlatformViewBoundingRect((*iter)->GetPath().getBounds(),
bounding_rect, transformMatrix)) {
break;
}
// TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably a hard geometry problem. but i bet most clipping actions are just simple shapes like RRect

// rect. See `ClipRRectContainsPlatformViewBoundingRect`.
// https://github.com/flutter/flutter/issues/118650
[maskView clipPath:(*iter)->GetPath() matrix:transformMatrix];
clipView.maskView = maskView;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
kFloatCompareEpsilon);
}

- (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
- (void)testClipRectDoNotInterceptWithPlatformViewShouldNotAddMaskView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
Expand Down Expand Up @@ -1526,9 +1526,6 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 24, 24);
SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1);
stack.PushClipRRect(rrect);
// Push a clip path, big enough to contain the entire platform view bound
SkPath path = SkPath::RRect(SkRect::MakeXYWH(0, 0, 23, 23), 1, 1);
stack.PushClipPath(path);

auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), stack);
Expand All @@ -1546,6 +1543,70 @@ - (void)testClipsDoNotInterceptWithPlatformViewShouldNotAddMaskView {
XCTAssertNil(childClippingView.maskView);
}

- (void)testClipRRectOnlyHasCornersInterceptWithPlatformViewShouldAddMaskView {
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 flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners);

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, 30, 30)] 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::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
stack.PushTransform(screenScaleMatrix);
SkMatrix translateMatrix = SkMatrix::Translate(5, 5);
// The platform view's rect for this test will be (5, 5, 10, 10)
stack.PushTransform(translateMatrix);

// Push a clip rrect, the rect of the rrect is the same as the PlatformView of the corner should
// clip the PlatformView.
SkRect rect_for_rrect = SkRect::MakeXYWH(0, 0, 10, 10);
SkRRect rrect = SkRRect::MakeRectXY(rect_for_rrect, 1, 1);
stack.PushClipRRect(rrect);

auto embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(
SkMatrix::Concat(screenScaleMatrix, translateMatrix), SkSize::Make(5, 5), 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];

XCTAssertNotNil(childClippingView.maskView);
}

- (void)testClipRect {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
Expand Down
3 changes: 3 additions & 0 deletions testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ - (BOOL)application:(UIApplication*)application
@"platform_view_multiple_background_foreground",
@"--platform-view-cliprect" : @"platform_view_cliprect",
@"--platform-view-cliprrect" : @"platform_view_cliprrect",
@"--platform-view-large-cliprrect" : @"platform_view_large_cliprrect",
@"--platform-view-clippath" : @"platform_view_clippath",
@"--platform-view-cliprrect-with-transform" : @"platform_view_cliprrect_with_transform",
@"--platform-view-large-cliprrect-with-transform" :
@"platform_view_large_cliprrect_with_transform",
@"--platform-view-cliprect-with-transform" : @"platform_view_cliprect_with_transform",
@"--platform-view-clippath-with-transform" : @"platform_view_clippath_with_transform",
@"--platform-view-transform" : @"platform_view_transform",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ - (instancetype)initWithLaunchArg:(NSString*)launchArg {
@"platform_view_multiple_background_foreground",
@"--platform-view-cliprect" : @"platform_view_cliprect",
@"--platform-view-cliprrect" : @"platform_view_cliprrect",
@"--platform-view-large-cliprrect" : @"platform_view_large_cliprrect",
@"--platform-view-clippath" : @"platform_view_clippath",
@"--platform-view-cliprrect-with-transform" : @"platform_view_cliprrect_with_transform",
@"--platform-view-large-cliprrect-with-transform" :
@"platform_view_large_cliprrect_with_transform",
@"--platform-view-cliprect-with-transform" : @"platform_view_cliprect_with_transform",
@"--platform-view-clippath-with-transform" : @"platform_view_clippath_with_transform",
@"--platform-view-transform" : @"platform_view_transform",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ - (void)testPlatformView {

@end

@interface PlatformViewMutationLargeClipRRectTests : GoldenPlatformViewTests

@end

@implementation PlatformViewMutationLargeClipRRectTests

- (instancetype)initWithInvocation:(NSInvocation*)invocation {
GoldenTestManager* manager =
[[GoldenTestManager alloc] initWithLaunchArg:@"--platform-view-large-cliprrect"];
return [super initWithManager:manager invocation:invocation];
}

- (void)testPlatformView {
[self checkPlatformViewGolden];
}

@end

@interface PlatformViewMutationClipPathTests : GoldenPlatformViewTests

@end
Expand Down Expand Up @@ -170,6 +188,24 @@ - (void)testPlatformView {

@end

@interface PlatformViewMutationLargeClipRRectWithTransformTests : GoldenPlatformViewTests

@end

@implementation PlatformViewMutationLargeClipRRectWithTransformTests

- (instancetype)initWithInvocation:(NSInvocation*)invocation {
GoldenTestManager* manager = [[GoldenTestManager alloc]
initWithLaunchArg:@"--platform-view-large-cliprrect-with-transform"];
return [super initWithManager:manager invocation:invocation];
}

- (void)testPlatformView {
[self checkPlatformViewGolden];
}

@end

@interface PlatformViewMutationClipPathWithTransformTests : GoldenPlatformViewTests

@end
Expand Down
83 changes: 83 additions & 0 deletions testing/scenario_app/lib/src/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,41 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario {
}
}


/// Platform view with clip rrect.
/// The bounding rect of the rrect is the same as PlatformView and only the corner radii clips the PlatformView.
class PlatformViewLargeClipRRectScenario extends PlatformViewScenario {
/// Constructs a platform view with large clip rrect scenario.
PlatformViewLargeClipRRectScenario(
PlatformDispatcher dispatcher, {
int id = 0,
}) : super(dispatcher, id: id);

@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.pushClipRRect(
RRect.fromLTRBAndCorners(
0,
0,
500,
500,
topLeft: const Radius.circular(15),
topRight: const Radius.circular(50),
bottomLeft: const Radius.circular(50),
),
);

addPlatformView(
id,
dispatcher: dispatcher,
sceneBuilder: builder,
);

finishBuilder(builder);
}
}

/// Platform view with clip path.
class PlatformViewClipPathScenario extends PlatformViewScenario {
/// Constructs a platform view with clip path scenario.
Expand Down Expand Up @@ -730,6 +765,54 @@ class PlatformViewClipRRectWithTransformScenario extends PlatformViewScenario {
}
}

/// Platform view with clip rrect after transformed.
/// The bounding rect of the rrect is the same as PlatformView and only the corner radii clips the PlatformView.
class PlatformViewLargeClipRRectWithTransformScenario extends PlatformViewScenario {
/// Constructs a platform view with large clip rrect with transform scenario.
PlatformViewLargeClipRRectWithTransformScenario(
PlatformDispatcher dispatcher, {
int id = 0,
}) : super(dispatcher, id: id);

@override
void onBeginFrame(Duration duration) {
final Matrix4 matrix4 = Matrix4.identity()
..rotateZ(1)
..scale(0.5, 0.5, 1.0)
..translate(1000.0, 100.0);

final SceneBuilder builder = SceneBuilder()..pushTransform(matrix4.storage);
builder.pushClipRRect(
RRect.fromLTRBAndCorners(
0,
0,
500,
500,
topLeft: const Radius.circular(15),
topRight: const Radius.circular(50),
bottomLeft: const Radius.circular(50),
),
);
addPlatformView(
id,
dispatcher: dispatcher,
sceneBuilder: builder,
);

// Add a translucent rect that has the same size of PlatformView.
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawRect(
const Rect.fromLTWH(0, 0, 500, 500),
Paint()..color = const Color(0x22FF0000),
);
final Picture picture = recorder.endRecording();
builder.addPicture(Offset.zero, picture);

finishBuilder(builder);
}
}

/// Platform view with clip path after transformed.
class PlatformViewClipPathWithTransformScenario extends PlatformViewScenario {
/// Constructs a platform view with clip path with transform scenario.
Expand Down
2 changes: 2 additions & 0 deletions testing/scenario_app/lib/src/scenarios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ Map<String, ScenarioFactory> _scenarios = <String, ScenarioFactory>{
'platform_view_cliprect': () => PlatformViewClipRectScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_cliprect_with_transform': () => PlatformViewClipRectWithTransformScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_cliprrect': () => PlatformViewClipRRectScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_large_cliprrect': () => PlatformViewLargeClipRRectScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_cliprrect_with_transform': () => PlatformViewClipRRectWithTransformScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_large_cliprrect_with_transform': () => PlatformViewLargeClipRRectWithTransformScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_clippath': () => PlatformViewClipPathScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_clippath_with_transform': () => PlatformViewClipPathWithTransformScenario(PlatformDispatcher.instance, id: _viewId++),
'platform_view_transform': () => PlatformViewTransformScenario(PlatformDispatcher.instance, id: _viewId++),
Expand Down