From b7b95e9372b58ec2a9e83c7c69964c36bfb8bcce Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 28 Oct 2022 09:43:14 -0700 Subject: [PATCH] Reland "PlatformView partial blur #36015" (flutter/engine#37086) --- engine/src/flutter/flow/embedded_views.cc | 6 +- engine/src/flutter/flow/embedded_views.h | 56 +- .../flow/layers/backdrop_filter_layer.cc | 3 +- .../flutter/flow/mutators_stack_unittests.cc | 30 +- .../shell_test_external_view_embedder.cc | 5 +- .../shell_test_external_view_embedder.h | 3 +- .../flutter/shell/common/shell_unittests.cc | 2 +- .../framework/Source/FlutterPlatformViews.mm | 52 +- .../Source/FlutterPlatformViewsTest.mm | 578 ++++++++++++------ .../Source/FlutterPlatformViews_Internal.h | 62 +- .../Source/FlutterPlatformViews_Internal.mm | 216 ++++--- .../darwin/ios/ios_external_view_embedder.h | 3 +- .../darwin/ios/ios_external_view_embedder.mm | 5 +- ...ackdrop_filter_iPhone 8_13.0_simulator.png | Bin 49083 -> 47294 bytes 14 files changed, 673 insertions(+), 348 deletions(-) diff --git a/engine/src/flutter/flow/embedded_views.cc b/engine/src/flutter/flow/embedded_views.cc index ebc1812755..396b89ffb4 100644 --- a/engine/src/flutter/flow/embedded_views.cc +++ b/engine/src/flutter/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/engine/src/flutter/flow/embedded_views.h b/engine/src/flutter/flow/embedded_views.h index 14ca2ba12b..79e35fd772 100644 --- a/engine/src/flutter/flow/embedded_views.h +++ b/engine/src/flutter/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/engine/src/flutter/flow/layers/backdrop_filter_layer.cc b/engine/src/flutter/flow/layers/backdrop_filter_layer.cc index 2c9f673a5a..fa89d8021d 100644 --- a/engine/src/flutter/flow/layers/backdrop_filter_layer.cc +++ b/engine/src/flutter/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/engine/src/flutter/flow/mutators_stack_unittests.cc b/engine/src/flutter/flow/mutators_stack_unittests.cc index 54b6b516e4..42ce6363d4 100644 --- a/engine/src/flutter/flow/mutators_stack_unittests.cc +++ b/engine/src/flutter/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/engine/src/flutter/shell/common/shell_test_external_view_embedder.cc b/engine/src/flutter/shell/common/shell_test_external_view_embedder.cc index 789ebc22d7..7d660b724b 100644 --- a/engine/src/flutter/shell/common/shell_test_external_view_embedder.cc +++ b/engine/src/flutter/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/engine/src/flutter/shell/common/shell_test_external_view_embedder.h b/engine/src/flutter/shell/common/shell_test_external_view_embedder.h index 583a09182e..5bff1c97eb 100644 --- a/engine/src/flutter/shell/common/shell_test_external_view_embedder.h +++ b/engine/src/flutter/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/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 32deedfdfb..fe89e63ae5 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 00d8324d11..8442e1eadd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -321,10 +321,11 @@ void FlutterPlatformViewsController::EndFrame( } 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 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators 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 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators 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 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators } 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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index b73373760c..9e678e5121 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -283,7 +283,7 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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); +} - // 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"]); +- (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)); + + 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 @@ fml::RefPtr CreateNewThread(std::string name) { // 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { // 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { // 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { // 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { // 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { } // 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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]); + PlatformViewFilter* platformViewFilter = + [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) + blurRadius:5 + visualEffectView:visualEffectView]; + XCTAssertNotNil(platformViewFilter); +} - // Invalid UIVisualEffectView initialization - ChildClippingView* childClippingView2 = [[ChildClippingView alloc] init]; - childClippingView2.blurEffectView = [[UIVisualEffectView alloc] init]; - XCTAssertFalse([childClippingView2 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 UIView - ChildClippingView* childClippingView3 = [[ChildClippingView alloc] init]; - childClippingView3.blurEffectView = [[UIView alloc] init]; - XCTAssertFalse([childClippingView3 applyBlurBackdropFilters:blurRadii]); - - // 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { } } - 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 @@ fml::RefPtr CreateNewThread(std::string name) { 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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { [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 @@ fml::RefPtr CreateNewThread(std::string name) { 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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 542f39b736..06180a9d04 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -45,18 +45,54 @@ @end -// The parent view handles clipping to its subviews. +// 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; + +- (instancetype)init NS_UNAVAILABLE; + +// 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 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; - -// 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; +// 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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index e3c3389080..9f46bf854e 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -55,26 +55,106 @@ void ResetAnchor(CALayer* layer) { layer.position = CGPointZero; } +CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); +} + +BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { + const CGFloat epsilon = 0.01; + return radius1 - radius2 < epsilon; +} + } // namespace flutter -@implementation ChildClippingView { - // A gaussianFilter from UIVisualEffectView that can be copied for new backdrop filters. - NSObject* _gaussianFilter; +@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 self; } -// 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; ++ (void)resetPreparation { + _preparedOnce = NO; + [_gaussianBlurFilter release]; + _gaussianBlurFilter = nil; + _indexOfBackdropView = -1; + _indexOfVisualEffectSubview = -1; } ++ (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")]) { + _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; + } + } + } else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) { + _indexOfVisualEffectSubview = i; + } + } + _preparedOnce = YES; +} + ++ (BOOL)isUIVisualEffectViewImplementationValid { + return _indexOfBackdropView > -1 && _indexOfVisualEffectSubview > -1 && _gaussianBlurFilter; +} + +- (void)dealloc { + [_backdropFilterView release]; + _backdropFilterView = nil; + + [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. @@ -87,92 +167,53 @@ void ResetAnchor(CALayer* layer) { return NO; } -// 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; - - for (UIView* view in self.blurEffectView.subviews) { - 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. - 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; - } - } - - return gaussianFilter; -} - -- (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]; - - if (!_gaussianFilter) { - FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to " - "access the gaussianBlur CAFilter."; - 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 ResetAnchor(CALayer* layer) { } - (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 ResetAnchor(CALayer* layer) { } 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 ResetAnchor(CALayer* layer) { 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 ResetAnchor(CALayer* layer) { 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/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.h b/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.h index 03cec910ba..8779f936db 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/engine/src/flutter/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/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.mm b/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.mm index 76995be2c5..3222bf2a09 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -100,8 +100,9 @@ bool IOSExternalViewEmbedder::SupportsDynamicThreadMerging() { // |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/engine/src/flutter/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png b/engine/src/flutter/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png index 307864c09a863c7b3b8e696d7c7c63a76968ab25..8fbee0e9eac2caefa834bfeefd9d9bc9a76d126a 100644 GIT binary patch literal 47294 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 S(ij*R7(8A5T-G@yGywn?j9N1Q