diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index ebc1812755488..396b89ffb447b 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -100,8 +100,10 @@ void MutatorsStack::PushOpacity(const int& alpha) { }; void MutatorsStack::PushBackdropFilter( - const std::shared_ptr& filter) { - std::shared_ptr element = std::make_shared(filter); + const std::shared_ptr& filter, + const SkRect& filter_rect) { + std::shared_ptr element = + std::make_shared(filter, filter_rect); vector_.push_back(element); }; diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 14ca2ba12b348..79e35fd7729ff 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -32,6 +32,33 @@ enum MutatorType { kBackdropFilter }; +// Represents an image filter mutation. +// +// Should be used for image_filter_layer and backdrop_filter_layer. +// TODO(cyanglaz): Refactor this into a ImageFilterMutator class. +// https://github.com/flutter/flutter/issues/108470 +class ImageFilterMutation { + public: + ImageFilterMutation(std::shared_ptr filter, + const SkRect& filter_rect) + : filter_(filter), filter_rect_(filter_rect) {} + + const DlImageFilter& GetFilter() const { return *filter_; } + const SkRect& GetFilterRect() const { return filter_rect_; } + + bool operator==(const ImageFilterMutation& other) const { + return *filter_ == *other.filter_ && filter_rect_ == other.filter_rect_; + } + + bool operator!=(const ImageFilterMutation& other) const { + return !operator==(other); + } + + private: + std::shared_ptr filter_; + const SkRect filter_rect_; +}; + // Stores mutation information like clipping or kTransform. // // The `type` indicates the type of the mutation: kClipRect, kTransform and etc. @@ -59,7 +86,7 @@ class Mutator { alpha_ = other.alpha_; break; case kBackdropFilter: - filter_ = other.filter_; + filter_mutation_ = other.filter_mutation_; break; default: break; @@ -73,15 +100,20 @@ class Mutator { explicit Mutator(const SkMatrix& matrix) : type_(kTransform), matrix_(matrix) {} explicit Mutator(const int& alpha) : type_(kOpacity), alpha_(alpha) {} - explicit Mutator(std::shared_ptr filter) - : type_(kBackdropFilter), filter_(filter) {} + explicit Mutator(std::shared_ptr filter, + const SkRect& filter_rect) + : type_(kBackdropFilter), + filter_mutation_( + std::make_shared(filter, filter_rect)) {} const MutatorType& GetType() const { return type_; } const SkRect& GetRect() const { return rect_; } const SkRRect& GetRRect() const { return rrect_; } const SkPath& GetPath() const { return *path_; } const SkMatrix& GetMatrix() const { return matrix_; } - const DlImageFilter& GetFilter() const { return *filter_; } + const ImageFilterMutation& GetFilterMutation() const { + return *filter_mutation_; + } const int& GetAlpha() const { return alpha_; } float GetAlphaFloat() const { return (alpha_ / 255.0); } @@ -101,7 +133,7 @@ class Mutator { case kOpacity: return alpha_ == other.alpha_; case kBackdropFilter: - return *filter_ == *other.filter_; + return *filter_mutation_ == *other.filter_mutation_; } return false; @@ -132,8 +164,7 @@ class Mutator { int alpha_; }; - std::shared_ptr filter_; - + std::shared_ptr filter_mutation_; }; // Mutator // A stack of mutators that can be applied to an embedded platform view. @@ -154,7 +185,8 @@ class MutatorsStack { void PushClipPath(const SkPath& path); void PushTransform(const SkMatrix& matrix); void PushOpacity(const int& alpha); - void PushBackdropFilter(const std::shared_ptr& filter); + void PushBackdropFilter(const std::shared_ptr& filter, + const SkRect& filter_rect); // Removes the `Mutator` on the top of the stack // and destroys it. @@ -249,8 +281,9 @@ class EmbeddedViewParams { const SkRect& finalBoundingRect() const { return final_bounding_rect_; } // Pushes the stored DlImageFilter object to the mutators stack. - void PushImageFilter(std::shared_ptr filter) { - mutators_stack_.PushBackdropFilter(filter); + void PushImageFilter(std::shared_ptr filter, + const SkRect& filter_rect) { + mutators_stack_.PushBackdropFilter(filter, filter_rect); } // Whether the embedder should construct DisplayList objects to hold the @@ -454,7 +487,8 @@ class ExternalViewEmbedder { // See also: |PushVisitedPlatformView| for pushing platform view ids to the // visited platform views list. virtual void PushFilterToVisitedPlatformViews( - std::shared_ptr filter) {} + std::shared_ptr filter, + const SkRect& filter_rect) {} private: bool used_this_frame_ = false; diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 2c9f673a5a4f0..fa89d8021d08a 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -44,7 +44,8 @@ void BackdropFilterLayer::Preroll(PrerollContext* context, Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_)); if (context->view_embedder != nullptr) { - context->view_embedder->PushFilterToVisitedPlatformViews(filter_); + context->view_embedder->PushFilterToVisitedPlatformViews( + filter_, context->cull_rect); } SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); diff --git a/flow/mutators_stack_unittests.cc b/flow/mutators_stack_unittests.cc index 54b6b516e4a72..42ce6363d47ff 100644 --- a/flow/mutators_stack_unittests.cc +++ b/flow/mutators_stack_unittests.cc @@ -94,14 +94,19 @@ TEST(MutatorsStack, PushBackdropFilter) { const int num_of_mutators = 10; for (int i = 0; i < num_of_mutators; i++) { auto filter = std::make_shared(i, 5, DlTileMode::kClamp); - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(i, i, i, i)); } auto iter = stack.Begin(); int i = 0; while (iter != stack.End()) { ASSERT_EQ(iter->get()->GetType(), MutatorType::kBackdropFilter); - ASSERT_EQ(iter->get()->GetFilter().asBlur()->sigma_x(), i); + ASSERT_EQ(iter->get()->GetFilterMutation().GetFilter().asBlur()->sigma_x(), + i); + ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().x(), i); + ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().x(), i); + ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().width(), i); + ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().height(), i); ++iter; ++i; } @@ -164,7 +169,7 @@ TEST(MutatorsStack, Equality) { int alpha = 240; stack.PushOpacity(alpha); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeEmpty()); MutatorsStack stack_other; SkMatrix matrix_other = SkMatrix::Scale(1, 1); @@ -179,7 +184,7 @@ TEST(MutatorsStack, Equality) { stack_other.PushOpacity(other_alpha); auto other_filter = std::make_shared(5, 5, DlTileMode::kClamp); - stack_other.PushBackdropFilter(other_filter); + stack_other.PushBackdropFilter(other_filter, SkRect::MakeEmpty()); ASSERT_TRUE(stack == stack_other); } @@ -211,9 +216,9 @@ TEST(Mutator, Initialization) { ASSERT_TRUE(mutator5.GetType() == MutatorType::kOpacity); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter); + Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); ASSERT_TRUE(mutator6.GetType() == MutatorType::kBackdropFilter); - ASSERT_TRUE(mutator6.GetFilter() == *filter); + ASSERT_TRUE(mutator6.GetFilterMutation().GetFilter() == *filter); } TEST(Mutator, CopyConstructor) { @@ -244,7 +249,7 @@ TEST(Mutator, CopyConstructor) { ASSERT_TRUE(mutator5 == copy5); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter); + Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); Mutator copy6 = Mutator(mutator6); ASSERT_TRUE(mutator6 == copy6); } @@ -276,9 +281,10 @@ TEST(Mutator, Equality) { Mutator other_mutator5 = Mutator(alpha); ASSERT_TRUE(mutator5 == other_mutator5); - auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter); - Mutator other_mutator6 = Mutator(filter); + auto filter1 = std::make_shared(5, 5, DlTileMode::kClamp); + auto filter2 = std::make_shared(5, 5, DlTileMode::kClamp); + Mutator mutator6 = Mutator(filter1, SkRect::MakeEmpty()); + Mutator other_mutator6 = Mutator(filter2, SkRect::MakeEmpty()); ASSERT_TRUE(mutator6 == other_mutator6); } @@ -299,8 +305,8 @@ TEST(Mutator, UnEquality) { auto filter = std::make_shared(5, 5, DlTileMode::kClamp); auto filter2 = std::make_shared(10, 10, DlTileMode::kClamp); - Mutator mutator3 = Mutator(filter); - Mutator other_mutator3 = Mutator(filter2); + Mutator mutator3 = Mutator(filter, SkRect::MakeEmpty()); + Mutator other_mutator3 = Mutator(filter2, SkRect::MakeEmpty()); ASSERT_TRUE(mutator3 != other_mutator3); } diff --git a/shell/common/shell_test_external_view_embedder.cc b/shell/common/shell_test_external_view_embedder.cc index 789ebc22d72b8..7d660b724b2f2 100644 --- a/shell/common/shell_test_external_view_embedder.cc +++ b/shell/common/shell_test_external_view_embedder.cc @@ -89,10 +89,11 @@ void ShellTestExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) { // |ExternalViewEmbedder| void ShellTestExternalViewEmbedder::PushFilterToVisitedPlatformViews( - std::shared_ptr filter) { + std::shared_ptr filter, + const SkRect& filter_rect) { for (int64_t id : visited_platform_views_) { EmbeddedViewParams params = current_composition_params_[id]; - params.PushImageFilter(filter); + params.PushImageFilter(filter, filter_rect); current_composition_params_[id] = params; mutators_stacks_[id] = params.mutatorsStack(); } diff --git a/shell/common/shell_test_external_view_embedder.h b/shell/common/shell_test_external_view_embedder.h index 583a09182e5fc..5bff1c97ebdc9 100644 --- a/shell/common/shell_test_external_view_embedder.h +++ b/shell/common/shell_test_external_view_embedder.h @@ -76,7 +76,8 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - std::shared_ptr filter) override; + std::shared_ptr filter, + const SkRect& filter_rect) override; // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 32deedfdfbc37..fe89e63ae5678 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -845,7 +845,7 @@ TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) { auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp); auto mutator = *external_view_embedder->GetStack(50).Begin(); ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter); - ASSERT_EQ(mutator->GetFilter(), filter); + ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), filter); DestroyShell(std::move(shell)); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 00d8324d11b55..8442e1eadd9cb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -321,10 +321,11 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews( - std::shared_ptr filter) { + std::shared_ptr filter, + const SkRect& filter_rect) { for (int64_t id : visited_platform_views_) { EmbeddedViewParams params = current_composition_params_[id]; - params.PushImageFilter(filter); + params.PushImageFilter(filter, filter_rect); current_composition_params_[id] = params; } } @@ -425,7 +426,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { CGRectGetWidth(flutter_view.bounds), CGRectGetHeight(flutter_view.bounds))] autorelease]; - NSMutableArray* blurRadii = [[[NSMutableArray alloc] init] autorelease]; + NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease]; auto iter = mutators_stack.Begin(); while (iter != mutators_stack.End()) { @@ -448,13 +449,35 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha; break; case kBackdropFilter: { - // We only support DlBlurImageFilter for BackdropFilter. - if ((*iter)->GetFilter().asBlur() && canApplyBlurBackdrop) { - // sigma_x is arbitrarily chosen as the radius value because Quartz sets - // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode - // is not supported in Quartz's gaussianBlur CAFilter, so it is not used - // to blur the PlatformView. - [blurRadii addObject:@((*iter)->GetFilter().asBlur()->sigma_x())]; + // Only support DlBlurImageFilter for BackdropFilter. + if (!(*iter)->GetFilterMutation().GetFilter().asBlur() || !canApplyBlurBackdrop) { + break; + } + CGRect filterRect = + flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect()); + // `filterRect` reprents the rect that should be filtered inside the `flutter_view_`. + // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be + // filtered. + if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) { + break; + } + CGRect intersection = CGRectIntersection(filterRect, clipView.frame); + CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView]; + // sigma_x is arbitrarily chosen as the radius value because Quartz sets + // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode + // is not supported in Quartz's gaussianBlur CAFilter, so it is not used + // to blur the PlatformView. + CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x(); + UIVisualEffectView* visualEffectView = [[[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] autorelease]; + PlatformViewFilter* filter = + [[[PlatformViewFilter alloc] initWithFrame:frameInClipView + blurRadius:blurRadius + visualEffectView:visualEffectView] autorelease]; + if (!filter) { + canApplyBlurBackdrop = NO; + } else { + [blurFilters addObject:filter]; } break; } @@ -463,15 +486,16 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } if (canApplyBlurBackdrop) { - canApplyBlurBackdrop = [clipView applyBlurBackdropFilters:blurRadii]; + [clipView applyBlurBackdropFilters:blurFilters]; } // Reverse the offset of the clipView. // The clipView's frame includes the final translate of the final transform matrix. - // So we need to revese this translate so the platform view can layout at the correct offset. + // Thus, this translate needs to be reversed so the platform view can layout at the correct + // offset. // - // Note that we don't apply this transform matrix the clippings because clippings happen on the - // mask view, whose origin is always (0,0) to the flutter_view. + // Note that the transforms are not applied to the clipping paths because clipping paths happen on + // the mask view, whose origin is always (0,0) to the flutter_view. CATransform3D reverseTranslate = CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0); embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index b73373760c106..9e678e5121bd7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -283,7 +283,7 @@ - (void)testApplyBackdropFilter { stack.PushTransform(screenScaleMatrix); // Push a backdrop filter auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -297,15 +297,90 @@ - (void)testApplyBackdropFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has the CAFilter, no additional filters were added - XCTAssertEqual(1, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); +} + +- (void)testApplyBackdropFilterWithCorrectFrame { + 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(); + auto platform_view = std::make_unique( + /*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, 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::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale); + stack.PushTransform(screenScaleMatrix); + // Push a backdrop filter + auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 8, 8)); - // sigmaX is chosen for input radius, regardless of sigmaY - NSObject* gaussianFilter = [childClippingView.layer.filters firstObject]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + auto embeddedViewParams = + std::make_unique(screenScaleMatrix, SkSize::Make(5, 10), stack); + + flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); + flutterPlatformViewsController->CompositeEmbeddedView(2); + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [mockFlutterView addSubview:childClippingView]; + + [mockFlutterView setNeedsLayout]; + [mockFlutterView layoutIfNeeded]; + + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 5, 8) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); } - (void)testApplyMultipleBackdropFilters { @@ -349,7 +424,7 @@ - (void)testApplyMultipleBackdropFilters { // Push backdrop filters for (int i = 0; i < 50; i++) { auto filter = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } auto embeddedViewParams = @@ -364,17 +439,19 @@ - (void)testApplyMultipleBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(50, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // All filters have sigma X radius - for (int i = 0; i < 50; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(i), [gaussianFilter valueForKey:@"inputRadius"]); + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView); } - (void)testAddBackdropFilters { @@ -417,7 +494,7 @@ - (void)testAddBackdropFilters { stack.PushTransform(screenScaleMatrix); // Push a backdrop filter auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -431,10 +508,19 @@ - (void)testAddBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has the CAFilter, no additional filters were added - XCTAssertEqual(1, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); // // Simulate adding 1 backdrop filter (create a new mutators stack) @@ -444,7 +530,7 @@ - (void)testAddBackdropFilters { stack2.PushTransform(screenScaleMatrix); // Push backdrop filters for (int i = 0; i < 2; i++) { - stack2.PushBackdropFilter(filter); + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -455,17 +541,20 @@ - (void)testAddBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(2, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 2u); - // All filters have sigma X radius - for (int i = 0; i < 2; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 2u); } - (void)testRemoveBackdropFilters { @@ -509,7 +598,7 @@ - (void)testRemoveBackdropFilters { // Push backdrop filters auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } auto embeddedViewParams = @@ -531,7 +620,7 @@ - (void)testRemoveBackdropFilters { stack2.PushTransform(screenScaleMatrix); // Push backdrop filters for (int i = 0; i < 4; i++) { - stack2.PushBackdropFilter(filter); + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -542,18 +631,19 @@ - (void)testRemoveBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(4, (int)[childClippingView.layer.filters count]); - - // All filters have sigma X radius - for (int i = 0; i < 4; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } } - - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); // Simulate removing all backdrop filters (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -570,10 +660,13 @@ - (void)testRemoveBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has no CAFilters because no backdrop filters were added - XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); } - (void)testEditBackdropFilters { @@ -617,7 +710,7 @@ - (void)testEditBackdropFilters { // Push backdrop filters auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter); + stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } auto embeddedViewParams = @@ -643,11 +736,11 @@ - (void)testEditBackdropFilters { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2); + stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(filter); + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -658,21 +751,23 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(5, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The edited backdrop filter has the new radius value - for (int i = 0; i < 5; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - if (i == 3) { - XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]); - } else { - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u); + CGFloat expectInputRadius = 5; + if (numberOfExpectedVisualEffectView == 3) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + numberOfExpectedVisualEffectView++; } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); // Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -684,11 +779,11 @@ - (void)testEditBackdropFilters { if (i == 0) { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2); + stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(filter); + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -699,21 +794,23 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(5, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The edited backdrop filter has the new radius value - for (int i = 0; i < 5; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - if (i == 0) { - XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]); - } else { - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u); + CGFloat expectInputRadius = 5; + if (numberOfExpectedVisualEffectView == 0) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + numberOfExpectedVisualEffectView++; } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); // Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -725,11 +822,11 @@ - (void)testEditBackdropFilters { if (i == 4) { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2); + stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(filter); + stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -740,21 +837,23 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(5, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The edited backdrop filter has the new radius value - for (int i = 0; i < 5; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - if (i == 4) { - XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]); - } else { - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u); + CGFloat expectInputRadius = 5; + if (numberOfExpectedVisualEffectView == 4) { + expectInputRadius = 2; + } + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)expectInputRadius]) { + numberOfExpectedVisualEffectView++; } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); // Simulate editing all backdrop filters in the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -765,7 +864,7 @@ - (void)testEditBackdropFilters { for (int i = 0; i < 5; i++) { auto filter2 = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2); + stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -776,18 +875,19 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has CAFilters for the multiple backdrop filters - XCTAssertEqual(5, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The edited backdrop filter has the new radius value - for (int i = 0; i < 5; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - - XCTAssertEqualObjects(@(i), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); } - (void)testApplyBackdropFilterNotDlBlurImageFilter { @@ -830,7 +930,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { stack.PushTransform(screenScaleMatrix); // Push a dilate backdrop filter auto dilateFilter = std::make_shared(5, 2); - stack.PushBackdropFilter(dilateFilter); + stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty()); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -839,15 +939,19 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { flutterPlatformViewsController->CompositeEmbeddedView(2); XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [mockFlutterView addSubview:childClippingView]; [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // No filters were added - XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators // stack) Create embedded view params @@ -859,11 +963,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { for (int i = 0; i < 5; i++) { if (i == 2) { - stack2.PushBackdropFilter(dilateFilter); + stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(blurFilter); + stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -874,17 +978,19 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // Filters were only added for DlBlurImageFilters - XCTAssertEqual(4, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The added filters are all gaussianBlur filters - for (int i = 0; i < 4; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); // Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators // stack) Update embedded view params, delete except screenScaleMatrix @@ -894,11 +1000,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { // Push backdrop filters and dilate filter for (int i = 0; i < 5; i++) { if (i == 0) { - stack2.PushBackdropFilter(dilateFilter); + stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(blurFilter); + stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -909,17 +1015,19 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // Filters were only added for DlBlurImageFilters - XCTAssertEqual(4, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The added filters are all gaussianBlur filters - for (int i = 0; i < 4; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); // Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -929,11 +1037,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { // Push backdrop filters and dilate filter for (int i = 0; i < 5; i++) { if (i == 4) { - stack2.PushBackdropFilter(dilateFilter); + stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); continue; } - stack2.PushBackdropFilter(blurFilter); + stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -944,17 +1052,19 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // Filters were only added for DlBlurImageFilters - XCTAssertEqual(4, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // The added filters are all gaussianBlur filters - for (int i = 0; i < 4; i++) { - NSObject* gaussianFilter = childClippingView.layer.filters[i]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:(CGFloat)5]) { + numberOfExpectedVisualEffectView++; + } } + XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -963,7 +1073,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { } // Push dilate filters for (int i = 0; i < 5; i++) { - stack2.PushBackdropFilter(dilateFilter); + stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -974,38 +1084,45 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // No filters were added - XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if ([subview isKindOfClass:[UIVisualEffectView class]]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); } -- (void)testApplyBackdropFilterAPIChanged { - NSArray* blurRadii = @[ @(1), @(5), @(10) ]; - - // The gaussianBlur filter is extracted once for each childClippingView. - // Each test requires a new childClippingView +- (void)testApplyBackdropFilterCorrectAPI { + [PlatformViewFilter resetPreparation]; + // The gaussianBlur filter is extracted from UIVisualEffectView. + // Each test requires a new PlatformViewFilter // Valid UIVisualEffectView API - ChildClippingView* childClippingView1 = [[ChildClippingView alloc] init]; - childClippingView1.blurEffectView = [[UIVisualEffectView alloc] + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - XCTAssertTrue([childClippingView1 applyBlurBackdropFilters:blurRadii]); - - // Invalid UIVisualEffectView initialization - ChildClippingView* childClippingView2 = [[ChildClippingView alloc] init]; - childClippingView2.blurEffectView = [[UIVisualEffectView alloc] init]; - XCTAssertFalse([childClippingView2 applyBlurBackdropFilters:blurRadii]); + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + XCTAssertNotNil(platformViewFilter); +} - // Invalid UIView - ChildClippingView* childClippingView3 = [[ChildClippingView alloc] init]; - childClippingView3.blurEffectView = [[UIView alloc] init]; - XCTAssertFalse([childClippingView3 applyBlurBackdropFilters:blurRadii]); +- (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init]; + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + XCTAssertNil(platformViewFilter); +} - // Invalid UIVisualEffectView API for "name" - UIVisualEffectView* editedUIVisualEffectView1 = [[UIVisualEffectView alloc] +- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews1 = editedUIVisualEffectView1.subviews; - for (UIView* view in subviews1) { + NSArray* subviews = editedUIVisualEffectView.subviews; + for (UIView* view in subviews) { if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { for (CIFilter* filter in view.layer.filters) { if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { @@ -1016,16 +1133,19 @@ - (void)testApplyBackdropFilterAPIChanged { break; } } + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:editedUIVisualEffectView]; + XCTAssertNil(platformViewFilter); +} - ChildClippingView* childClippingView4 = [[ChildClippingView alloc] init]; - childClippingView4.blurEffectView = editedUIVisualEffectView1; - XCTAssertFalse([childClippingView4 applyBlurBackdropFilters:blurRadii]); - - // Invalid UIVisualEffectView API for "inputRadius" - UIVisualEffectView* editedUIVisualEffectView2 = [[UIVisualEffectView alloc] +- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius { + [PlatformViewFilter resetPreparation]; + UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews2 = editedUIVisualEffectView2.subviews; - for (UIView* view in subviews2) { + NSArray* subviews = editedUIVisualEffectView.subviews; + for (UIView* view in subviews) { if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { for (CIFilter* filter in view.layer.filters) { if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { @@ -1037,9 +1157,28 @@ - (void)testApplyBackdropFilterAPIChanged { } } - ChildClippingView* childClippingView5 = [[ChildClippingView alloc] init]; - childClippingView5.blurEffectView = editedUIVisualEffectView2; - XCTAssertFalse([childClippingView5 applyBlurBackdropFilters:blurRadii]); + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:editedUIVisualEffectView]; + XCTAssertNil(platformViewFilter); +} + +- (void)testBackdropFilterVisualEffectSubviewBackgroundColor { + UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] + initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + CGColorRef visualEffectSubviewBackgroundColor; + for (UIView* view in [platformViewFilter backdropFilterView].subviews) { + if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) { + visualEffectSubviewBackgroundColor = view.layer.backgroundColor; + } + } + XCTAssertTrue( + CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor)); } - (void)testCompositePlatformView { @@ -1142,7 +1281,8 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); flutterPlatformViewsController->PushVisitedPlatformView(2); auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter); + flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter, + SkRect::MakeXYWH(0, 0, 10, 10)); flutterPlatformViewsController->CompositeEmbeddedView(2); XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -1151,15 +1291,20 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // childClippingView has the CAFilter, no additional filters were added - XCTAssertEqual(1, (int)[childClippingView.layer.filters count]); - // No new views were added - XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); - - // sigmaX is chosen for input radius, regardless of sigmaY - NSObject* gaussianFilter = [childClippingView.layer.filters firstObject]; - XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]); - XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]); + // childClippingView has visual effect view with the correct configurations. + NSUInteger numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u); + if ([self validateOneVisualEffectView:subview + expectedFrame:CGRectMake(0, 0, 10, 10) + inputRadius:5]) { + numberOfExpectedVisualEffectView++; + } + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); // New frame, with no filter pushed. auto embeddedViewParams2 = @@ -1172,8 +1317,14 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - // No filter in this frame. - XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); + numberOfExpectedVisualEffectView = 0; + for (UIView* subview in childClippingView.subviews) { + if (![subview isKindOfClass:[UIVisualEffectView class]]) { + continue; + } + numberOfExpectedVisualEffectView++; + } + XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); } - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { @@ -2083,4 +2234,31 @@ - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstRe XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree); } +// Return true if a correct visual effect view is found. It also implies all the validation in this +// method passes. +// +// There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No +// correct visual effect view found. +- (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView + expectedFrame:(CGRect)frame + inputRadius:(CGFloat)inputRadius { + XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame)); + for (UIView* view in visualEffectView.subviews) { + if (![view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { + continue; + } + XCTAssertEqual(view.layer.filters.count, 1u); + NSObject* filter = view.layer.filters.firstObject; + + XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur"); + + NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"]; + XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] && + flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue, + inputRadius)); + return YES; + } + return NO; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 542f39b736f4f..06180a9d04c78 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -45,18 +45,54 @@ @end -// The parent view handles clipping to its subviews. -@interface ChildClippingView : UIView +// An object represents a blur filter. +// +// This object produces a `backdropFilterView`. +// To blur a View, add `backdropFilterView` as a subView of the View. +@interface PlatformViewFilter : NSObject + +// Determines the rect of the blur effect in the coordinate system of `backdropFilterView`'s +// parentView. +@property(assign, nonatomic, readonly) CGRect frame; + +// Determines the blur intensity. +// +// It is set as the value of `inputRadius` of the `gaussianFilter` that is internally used. +@property(assign, nonatomic, readonly) CGFloat blurRadius; + +// This is the view to use to blur the PlatformView. +// +// It is a modified version of UIKit's `UIVisualEffectView`. +// The inputRadius can be customized and it doesn't add any color saturation to the blurred view. +@property(nonatomic, retain, readonly) UIVisualEffectView* backdropFilterView; + +// For testing only. ++ (void)resetPreparation; -// Applies blur backdrop filters to the ChildClippingView with blur radius values from -// blurRadii. Returns NO if Apple's API has changed and blurred backdrop filters cannot -// be applied, otherwise returns YES. -- (BOOL)applyBlurBackdropFilters:(NSArray*)blurRadii; +- (instancetype)init NS_UNAVAILABLE; -// The UIView used to extract the gaussianBlur filter. This must be a UIVisualEffectView -// initalized with UIBlurEffect to extract the correct filter. Made a public property -// for custom unit tests. -@property(nonatomic, retain) UIView* blurEffectView; +// Initialize the filter object. +// +// The `frame` determines the rect of the blur effect in the coordinate system of +// `backdropFilterView`'s parentView. The `blurRadius` determines the blur intensity. It is set as +// the value of `inputRadius` of the `gaussianFilter` that is internally used. The +// `UIVisualEffectView` is the view that is used to add the blur effects. It is modified to become +// `backdropFilterView`, which better supports the need of Flutter. +// +// Note: if the implementation of UIVisualEffectView changes in a way that affects the +// implementation in `PlatformViewFilter`, this method will return nil. +- (instancetype)initWithFrame:(CGRect)frame + blurRadius:(CGFloat)blurRadius + visualEffectView:(UIVisualEffectView*)visualEffectView NS_DESIGNATED_INITIALIZER; + +@end + +// The parent view handles clipping to its subViews. +@interface ChildClippingView : UIView + +// Applies blur backdrop filters to the ChildClippingView with blur values from +// filters. +- (void)applyBlurBackdropFilters:(NSMutableArray*)filters; @end @@ -69,6 +105,9 @@ CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix); // The position of the `layer` should be unchanged after resetting the anchor. void ResetAnchor(CALayer* layer); +CGRect GetCGRectFromSkRect(const SkRect& clipSkRect); +BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2); + class IOSContextGL; class IOSSurface; @@ -197,7 +236,8 @@ class FlutterPlatformViewsController { long FindFirstResponderPlatformViewId(); // Pushes backdrop filter mutation to the mutator stack of each visited platform view. - void PushFilterToVisitedPlatformViews(std::shared_ptr filter); + void PushFilterToVisitedPlatformViews(std::shared_ptr filter, + const SkRect& filter_rect); // Pushes the view id of a visted platform view to the list of visied platform views. void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index e3c3389080a7d..9f46bf854e75d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -55,124 +55,165 @@ void ResetAnchor(CALayer* layer) { layer.position = CGPointZero; } -} // namespace flutter - -@implementation ChildClippingView { - // A gaussianFilter from UIVisualEffectView that can be copied for new backdrop filters. - NSObject* _gaussianFilter; +CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); } -// Lazy initializes blurEffectView as the expected UIVisualEffectView. The backdropFilter blur -// requires this UIVisualEffectView initialization. The lazy initalization is only used to allow -// custom unit tests. -- (UIView*)blurEffectView { - if (!_blurEffectView) { - // blurEffectView is only needed to extract its gaussianBlur filter. It is released after - // searching its subviews and extracting the filter. - _blurEffectView = [[[UIVisualEffectView alloc] - initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] retain]; - } - return _blurEffectView; +BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { + const CGFloat epsilon = 0.01; + return radius1 - radius2 < epsilon; } -// The ChildClippingView's frame is the bounding rect of the platform view. we only want touches to -// be hit tested and consumed by this view if they are inside the embedded platform view which could -// be smaller the embedded platform view is rotated. -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { - for (UIView* view in self.subviews) { - if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) { - return YES; +} // namespace flutter + +@implementation PlatformViewFilter + +static NSObject* _gaussianBlurFilter = nil; +// The index of "_UIVisualEffectBackdropView" in UIVisualEffectView's subViews. +static NSInteger _indexOfBackdropView = -1; +// The index of "_UIVisualEffectSubview" in UIVisualEffectView's subViews. +static NSInteger _indexOfVisualEffectSubview = -1; +static BOOL _preparedOnce = NO; + +- (instancetype)initWithFrame:(CGRect)frame + blurRadius:(CGFloat)blurRadius + visualEffectView:(UIVisualEffectView*)visualEffectView { + if (self = [super init]) { + _frame = frame; + _blurRadius = blurRadius; + [PlatformViewFilter prepareOnce:visualEffectView]; + if (![PlatformViewFilter isUIVisualEffectViewImplementationValid]) { + FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to " + "access the gaussianBlur CAFilter."; + [self release]; + return nil; } + NSObject* gaussianBlurFilter = [[_gaussianBlurFilter copy] autorelease]; + FML_DCHECK(gaussianBlurFilter); + UIView* backdropView = visualEffectView.subviews[_indexOfBackdropView]; + [gaussianBlurFilter setValue:@(_blurRadius) forKey:@"inputRadius"]; + backdropView.layer.filters = @[ gaussianBlurFilter ]; + + UIView* visualEffectSubview = visualEffectView.subviews[_indexOfVisualEffectSubview]; + visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor; + + _backdropFilterView = [visualEffectView retain]; + _backdropFilterView.frame = _frame; } - return NO; + return self; } -// Creates and initializes a UIVisualEffectView with a UIBlurEffect. Extracts and returns its -// gaussianFilter. Returns nil if Apple's API has changed and the filter cannot be extracted. -- (NSObject*)extractGaussianFilter { - NSObject* gaussianFilter = nil; ++ (void)resetPreparation { + _preparedOnce = NO; + [_gaussianBlurFilter release]; + _gaussianBlurFilter = nil; + _indexOfBackdropView = -1; + _indexOfVisualEffectSubview = -1; +} - for (UIView* view in self.blurEffectView.subviews) { ++ (void)prepareOnce:(UIVisualEffectView*)visualEffectView { + if (_preparedOnce) { + return; + } + for (NSUInteger i = 0; i < visualEffectView.subviews.count; i++) { + UIView* view = visualEffectView.subviews[i]; if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { - for (CIFilter* filter in view.layer.filters) { - if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { - if ([[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) { - gaussianFilter = filter; - } - // No need to look at other CIFilters. If the API structure has not changed, the - // gaussianBlur filter was succesfully saved. Otherwise, still exit the loop because the - // filter cannot be extracted. + _indexOfBackdropView = i; + for (NSObject* filter in view.layer.filters) { + if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"] && + [[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) { + _gaussianBlurFilter = [filter retain]; break; } } - // No need to look at other UIViews. If the API structure has not changed, the gaussianBlur - // filter was succesfully saved. Otherwise, still exit the loop because the filter cannot - // be extracted. - break; + } else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) { + _indexOfVisualEffectSubview = i; } } + _preparedOnce = YES; +} - return gaussianFilter; ++ (BOOL)isUIVisualEffectViewImplementationValid { + return _indexOfBackdropView > -1 && _indexOfVisualEffectSubview > -1 && _gaussianBlurFilter; } -- (BOOL)applyBlurBackdropFilters:(NSArray*)blurRadii { - // The outer if-statement checks for the first time this method is called and _gaussianFilter is - // not initialized. The inner if-statement checks if extracting the gaussianBlur was successful. - // If it was not successful, this method will not be called again. Thus the if-statements check - // for different conditions. - if (!_gaussianFilter) { - _gaussianFilter = [self extractGaussianFilter]; +- (void)dealloc { + [_backdropFilterView release]; + _backdropFilterView = nil; - if (!_gaussianFilter) { - FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to " - "access the gaussianBlur CAFilter."; - return NO; + [super dealloc]; +} + +@end + +@interface ChildClippingView () + +@property(retain, nonatomic) NSMutableArray* filters; + +@end + +@implementation ChildClippingView + +// The ChildClippingView's frame is the bounding rect of the platform view. we only want touches to +// be hit tested and consumed by this view if they are inside the embedded platform view which could +// be smaller the embedded platform view is rotated. +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { + for (UIView* view in self.subviews) { + if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) { + return YES; } } + return NO; +} - BOOL newRadiusValues = NO; - - if ([blurRadii count] != [self.layer.filters count]) { - newRadiusValues = YES; +- (void)applyBlurBackdropFilters:(NSMutableArray*)filters { + BOOL needUpdateFilterViews = NO; + if (self.filters.count != filters.count) { + needUpdateFilterViews = YES; } else { - for (NSUInteger i = 0; i < [blurRadii count]; i++) { - if ([self.layer.filters[i] valueForKey:@"inputRadius"] != blurRadii[i]) { - newRadiusValues = YES; - break; + for (NSUInteger i = 0; i < filters.count; i++) { + if (!CGRectEqualToRect(self.filters[i].frame, filters[i].frame) || + !flutter::BlurRadiusEqualToBlurRadius(self.filters[i].blurRadius, + filters[i].blurRadius)) { + needUpdateFilterViews = YES; } } } - - if (newRadiusValues) { - NSMutableArray* newGaussianFilters = [[[NSMutableArray alloc] init] autorelease]; - - for (NSUInteger i = 0; i < [blurRadii count]; i++) { - NSObject* newGaussianFilter = [[_gaussianFilter copy] autorelease]; - [newGaussianFilter setValue:blurRadii[i] forKey:@"inputRadius"]; - [newGaussianFilters addObject:newGaussianFilter]; + if (needUpdateFilterViews) { + // Clear the old filter views. + for (PlatformViewFilter* filter in self.filters) { + [[filter backdropFilterView] removeFromSuperview]; + } + // Update to the new filters. + self.filters = [filters retain]; + // Add new filter views. + for (PlatformViewFilter* filter in self.filters) { + UIView* backdropFilterView = [filter backdropFilterView]; + [self addSubview:backdropFilterView]; } - - self.layer.filters = newGaussianFilters; } - - return YES; } - (void)dealloc { - [_blurEffectView release]; - _blurEffectView = nil; + [_filters release]; + _filters = nil; - [_gaussianFilter release]; - _gaussianFilter = nil; [super dealloc]; } +- (NSMutableArray*)filters { + if (!_filters) { + _filters = [[[NSMutableArray alloc] init] retain]; + } + return _filters; +} + @end @interface FlutterClippingMaskView () - (fml::CFRef)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix; -- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect; @end @@ -212,7 +253,7 @@ - (void)drawRect:(CGRect)rect { } - (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix { - CGRect clipRect = [self getCGRectFromSkRect:clipSkRect]; + CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect); CGPathRef path = CGPathCreateWithRect(clipRect, nil); paths_.push_back([self getTransformedPath:path matrix:matrix]); } @@ -229,7 +270,7 @@ - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matri } case SkRRect::kOval_Type: case SkRRect::kSimple_Type: { - CGRect clipRect = [self getCGRectFromSkRect:clipSkRRect.rect()]; + CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRRect.rect()); pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(), clipSkRRect.getSimpleRadii().y(), nil); break; @@ -296,7 +337,7 @@ - (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix { SkPath::Iter iter(path, true); SkPoint pts[kMaxPointsInVerb]; SkPath::Verb verb = iter.next(pts); - SkPoint last_pt_from_last_verb; + SkPoint last_pt_from_last_verb = SkPoint::Make(0, 0); while (verb != SkPath::kDone_Verb) { if (verb == SkPath::kLine_Verb || verb == SkPath::kQuad_Verb || verb == SkPath::kConic_Verb || verb == SkPath::kCubic_Verb) { @@ -353,9 +394,4 @@ - (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix { return fml::CFRef(transformedPath); } -- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect { - return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, - clipSkRect.fBottom - clipSkRect.fTop); -} - @end diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h index 03cec910bae39..8779f936db838 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -69,7 +69,8 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - std::shared_ptr filter) override; + std::shared_ptr filter, + const SkRect& filter_rect) override; // |ExternalViewEmbedder| void PushVisitedPlatformView(int64_t view_id) override; diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm index 76995be2c56ad..3222bf2a096c3 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -100,8 +100,9 @@ // |ExternalViewEmbedder| void IOSExternalViewEmbedder::PushFilterToVisitedPlatformViews( - std::shared_ptr filter) { - platform_views_controller_->PushFilterToVisitedPlatformViews(filter); + std::shared_ptr filter, + const SkRect& filter_rect) { + platform_views_controller_->PushFilterToVisitedPlatformViews(filter, filter_rect); } // |ExternalViewEmbedder| diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png index 307864c09a863..8fbee0e9eac2c 100644 Binary files a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png and b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png differ