mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland "PlatformView partial blur #36015" (flutter/engine#37086)
This commit is contained in:
parent
9dd73f3f2c
commit
b7b95e9372
@ -100,8 +100,10 @@ void MutatorsStack::PushOpacity(const int& alpha) {
|
||||
};
|
||||
|
||||
void MutatorsStack::PushBackdropFilter(
|
||||
const std::shared_ptr<const DlImageFilter>& filter) {
|
||||
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(filter);
|
||||
const std::shared_ptr<const DlImageFilter>& filter,
|
||||
const SkRect& filter_rect) {
|
||||
std::shared_ptr<Mutator> element =
|
||||
std::make_shared<Mutator>(filter, filter_rect);
|
||||
vector_.push_back(element);
|
||||
};
|
||||
|
||||
|
||||
@ -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<const DlImageFilter> 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<const DlImageFilter> 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<const DlImageFilter> filter)
|
||||
: type_(kBackdropFilter), filter_(filter) {}
|
||||
explicit Mutator(std::shared_ptr<const DlImageFilter> filter,
|
||||
const SkRect& filter_rect)
|
||||
: type_(kBackdropFilter),
|
||||
filter_mutation_(
|
||||
std::make_shared<ImageFilterMutation>(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<const DlImageFilter> filter_;
|
||||
|
||||
std::shared_ptr<ImageFilterMutation> 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<const DlImageFilter>& filter);
|
||||
void PushBackdropFilter(const std::shared_ptr<const DlImageFilter>& 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<const DlImageFilter> filter) {
|
||||
mutators_stack_.PushBackdropFilter(filter);
|
||||
void PushImageFilter(std::shared_ptr<const DlImageFilter> 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<const DlImageFilter> filter) {}
|
||||
std::shared_ptr<const DlImageFilter> filter,
|
||||
const SkRect& filter_rect) {}
|
||||
|
||||
private:
|
||||
bool used_this_frame_ = false;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<DlBlurImageFilter>(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<DlBlurImageFilter>(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<DlBlurImageFilter>(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<DlBlurImageFilter>(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<DlBlurImageFilter>(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<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
|
||||
Mutator mutator6 = Mutator(filter);
|
||||
Mutator other_mutator6 = Mutator(filter);
|
||||
auto filter1 = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
|
||||
auto filter2 = std::make_shared<DlBlurImageFilter>(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<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
|
||||
auto filter2 =
|
||||
std::make_shared<DlBlurImageFilter>(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);
|
||||
}
|
||||
|
||||
|
||||
@ -89,10 +89,11 @@ void ShellTestExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) {
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void ShellTestExternalViewEmbedder::PushFilterToVisitedPlatformViews(
|
||||
std::shared_ptr<const DlImageFilter> filter) {
|
||||
std::shared_ptr<const DlImageFilter> 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();
|
||||
}
|
||||
|
||||
@ -76,7 +76,8 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder {
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void PushFilterToVisitedPlatformViews(
|
||||
std::shared_ptr<const DlImageFilter> filter) override;
|
||||
std::shared_ptr<const DlImageFilter> filter,
|
||||
const SkRect& filter_rect) override;
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void SubmitFrame(GrDirectContext* context,
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -321,10 +321,11 @@ void FlutterPlatformViewsController::EndFrame(
|
||||
}
|
||||
|
||||
void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews(
|
||||
std::shared_ptr<const DlImageFilter> filter) {
|
||||
std::shared_ptr<const DlImageFilter> 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);
|
||||
|
||||
@ -283,7 +283,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
stack.PushTransform(screenScaleMatrix);
|
||||
// Push a backdrop filter
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
|
||||
stack.PushBackdropFilter(filter);
|
||||
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
|
||||
|
||||
auto embeddedViewParams =
|
||||
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
|
||||
@ -297,15 +297,90 @@ fml::RefPtr<fml::TaskRunner> 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<flutter::FlutterPlatformViewsController>();
|
||||
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
|
||||
/*delegate=*/mock_delegate,
|
||||
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
|
||||
/*platform_views_controller=*/flutterPlatformViewsController,
|
||||
/*task_runners=*/runners);
|
||||
|
||||
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
|
||||
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
|
||||
flutterPlatformViewsController->RegisterViewFactory(
|
||||
factory, @"MockFlutterPlatformView",
|
||||
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
|
||||
FlutterResult result = ^(id result) {
|
||||
};
|
||||
flutterPlatformViewsController->OnMethodCall(
|
||||
[FlutterMethodCall
|
||||
methodCallWithMethodName:@"create"
|
||||
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
|
||||
result);
|
||||
|
||||
XCTAssertNotNil(gMockPlatformView);
|
||||
|
||||
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 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<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
|
||||
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 8, 8));
|
||||
|
||||
auto embeddedViewParams =
|
||||
std::make_unique<flutter::EmbeddedViewParams>(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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
// Push backdrop filters
|
||||
for (int i = 0; i < 50; i++) {
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(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<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
stack.PushTransform(screenScaleMatrix);
|
||||
// Push a backdrop filter
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
|
||||
stack.PushBackdropFilter(filter);
|
||||
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
|
||||
|
||||
auto embeddedViewParams =
|
||||
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
|
||||
@ -431,10 +508,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -455,17 +541,20 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
// Push backdrop filters
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -542,18 +631,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
// Push backdrop filters
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
auto filter2 =
|
||||
std::make_shared<flutter::DlBlurImageFilter>(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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -658,21 +751,23 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
if (i == 0) {
|
||||
auto filter2 =
|
||||
std::make_shared<flutter::DlBlurImageFilter>(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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -699,21 +794,23 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
if (i == 4) {
|
||||
auto filter2 =
|
||||
std::make_shared<flutter::DlBlurImageFilter>(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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -740,21 +837,23 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
auto filter2 = std::make_shared<flutter::DlBlurImageFilter>(i, 2, flutter::DlTileMode::kClamp);
|
||||
|
||||
stack2.PushBackdropFilter(filter2);
|
||||
stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10));
|
||||
}
|
||||
|
||||
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -776,18 +875,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
stack.PushTransform(screenScaleMatrix);
|
||||
// Push a dilate backdrop filter
|
||||
auto dilateFilter = std::make_shared<flutter::DlDilateImageFilter>(5, 2);
|
||||
stack.PushBackdropFilter(dilateFilter);
|
||||
stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty());
|
||||
|
||||
auto embeddedViewParams =
|
||||
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
|
||||
@ -839,15 +939,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -874,17 +978,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -909,17 +1015,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -944,17 +1052,19 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::EmbeddedViewParams>(screenScaleMatrix,
|
||||
@ -974,38 +1084,45 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> CreateNewThread(std::string name) {
|
||||
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
|
||||
flutterPlatformViewsController->PushVisitedPlatformView(2);
|
||||
auto filter = std::make_shared<flutter::DlBlurImageFilter>(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<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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
|
||||
|
||||
@ -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<PlatformViewFilter*>*)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<const DlImageFilter> filter);
|
||||
void PushFilterToVisitedPlatformViews(std::shared_ptr<const DlImageFilter> 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); }
|
||||
|
||||
@ -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<PlatformViewFilter*>* 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<PlatformViewFilter*>*)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<CGPathRef>)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<CGPathRef>(transformedPath);
|
||||
}
|
||||
|
||||
- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
|
||||
return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
|
||||
clipSkRect.fBottom - clipSkRect.fTop);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -69,7 +69,8 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder {
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void PushFilterToVisitedPlatformViews(
|
||||
std::shared_ptr<const DlImageFilter> filter) override;
|
||||
std::shared_ptr<const DlImageFilter> filter,
|
||||
const SkRect& filter_rect) override;
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void PushVisitedPlatformView(int64_t view_id) override;
|
||||
|
||||
@ -100,8 +100,9 @@ bool IOSExternalViewEmbedder::SupportsDynamicThreadMerging() {
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
void IOSExternalViewEmbedder::PushFilterToVisitedPlatformViews(
|
||||
std::shared_ptr<const DlImageFilter> filter) {
|
||||
platform_views_controller_->PushFilterToVisitedPlatformViews(filter);
|
||||
std::shared_ptr<const DlImageFilter> filter,
|
||||
const SkRect& filter_rect) {
|
||||
platform_views_controller_->PushFilterToVisitedPlatformViews(filter, filter_rect);
|
||||
}
|
||||
|
||||
// |ExternalViewEmbedder|
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Loading…
x
Reference in New Issue
Block a user