diff --git a/engine/src/flutter/flow/embedded_views.cc b/engine/src/flutter/flow/embedded_views.cc index 57b5997faab..3fc44d0f4e8 100644 --- a/engine/src/flutter/flow/embedded_views.cc +++ b/engine/src/flutter/flow/embedded_views.cc @@ -96,6 +96,28 @@ void MutatorsStack::PushBackdropFilter( vector_.push_back(element); } +void MutatorsStack::PushPlatformViewClipRect(const DlRect& rect) { + std::shared_ptr element = + std::make_shared(BackdropClipRect(rect)); + vector_.push_back(element); +} +void MutatorsStack::PushPlatformViewClipRRect(const DlRoundRect& rrect) { + std::shared_ptr element = + std::make_shared(BackdropClipRRect(rrect)); + vector_.push_back(element); +} +void MutatorsStack::PushPlatformViewClipRSuperellipse( + const DlRoundSuperellipse& rse) { + std::shared_ptr element = + std::make_shared(BackdropClipRSuperellipse(rse)); + vector_.push_back(element); +} +void MutatorsStack::PushPlatformViewClipPath(const DlPath& path) { + std::shared_ptr element = + std::make_shared(BackdropClipPath(path)); + vector_.push_back(element); +} + void MutatorsStack::Pop() { vector_.pop_back(); } diff --git a/engine/src/flutter/flow/embedded_views.h b/engine/src/flutter/flow/embedded_views.h index b9ddd9f3ab3..0800e71ce20 100644 --- a/engine/src/flutter/flow/embedded_views.h +++ b/engine/src/flutter/flow/embedded_views.h @@ -10,6 +10,8 @@ #include #include +#include "display_list/geometry/dl_geometry_types.h" +#include "display_list/geometry/dl_path.h" #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/geometry/dl_geometry_conversions.h" #include "flutter/flow/surface_frame.h" @@ -37,7 +39,11 @@ enum class MutatorType { kClipPath, kTransform, kOpacity, - kBackdropFilter + kBackdropFilter, + kBackdropClipRect, + kBackdropClipRRect, + kBackdropClipRSuperellipse, + kBackdropClipPath, }; // Represents an image filter mutation. @@ -61,6 +67,42 @@ class ImageFilterMutation { const DlRect filter_rect_; }; +struct BackdropClipRect { + DlRect rect; + explicit BackdropClipRect(const DlRect& r) : rect(r) {} + + bool operator==(const BackdropClipRect& other) const { + return rect == other.rect; + } +}; + +struct BackdropClipRRect { + DlRoundRect rrect; + explicit BackdropClipRRect(const DlRoundRect& r) : rrect(r) {} + + bool operator==(const BackdropClipRRect& other) const { + return rrect == other.rrect; + } +}; + +struct BackdropClipRSuperellipse { + DlRoundSuperellipse rse; + explicit BackdropClipRSuperellipse(const DlRoundSuperellipse& r) : rse(r) {} + + bool operator==(const BackdropClipRSuperellipse& other) const { + return rse == other.rse; + } +}; + +struct BackdropClipPath { + DlPath path; + explicit BackdropClipPath(const DlPath& r) : path(r) {} + + bool operator==(const BackdropClipPath& other) const { + return path == other.path; + } +}; + // Stores mutation information like clipping or kTransform. // // The `type` indicates the type of the mutation: kClipRect, kTransform and etc. @@ -81,6 +123,15 @@ class Mutator { const DlRect& filter_rect) : data_(ImageFilterMutation(filter, filter_rect)) {} + explicit Mutator(const BackdropClipRect& backdrop_rect) + : data_(backdrop_rect) {} + explicit Mutator(const BackdropClipRRect& backdrop_rrect) + : data_(backdrop_rrect) {} + explicit Mutator(const BackdropClipRSuperellipse& backdrop_rse) + : data_(backdrop_rse) {} + explicit Mutator(const BackdropClipPath& backdrop_path) + : data_(backdrop_path) {} + MutatorType GetType() const { return static_cast(data_.index()); } @@ -98,6 +149,18 @@ class Mutator { const ImageFilterMutation& GetFilterMutation() const { return std::get(data_); } + const BackdropClipRect& GetBackdropClipRect() const { + return std::get(data_); + } + const BackdropClipRRect& GetBackdropClipRRect() const { + return std::get(data_); + } + const BackdropClipRSuperellipse& GetBackdropClipRSuperellipse() const { + return std::get(data_); + } + const BackdropClipPath& GetBackdropClipPath() const { + return std::get(data_); + } const uint8_t& GetAlpha() const { return std::get(data_); } float GetAlphaFloat() const { return DlColor::toOpacity(GetAlpha()); } @@ -110,6 +173,10 @@ class Mutator { case MutatorType::kClipRRect: case MutatorType::kClipRSE: return true; + case MutatorType::kBackdropClipRect: + case MutatorType::kBackdropClipRRect: + case MutatorType::kBackdropClipRSuperellipse: + case MutatorType::kBackdropClipPath: case MutatorType::kOpacity: case MutatorType::kTransform: case MutatorType::kBackdropFilter: @@ -124,7 +191,11 @@ class Mutator { DlPath, DlMatrix, uint8_t, - ImageFilterMutation> + ImageFilterMutation, + BackdropClipRect, + BackdropClipRRect, + BackdropClipRSuperellipse, + BackdropClipPath> data_; }; // Mutator @@ -150,6 +221,10 @@ class MutatorsStack { // `filter_rect` is in global coordinates. void PushBackdropFilter(const std::shared_ptr& filter, const DlRect& filter_rect); + void PushPlatformViewClipRect(const DlRect& rect); + void PushPlatformViewClipRRect(const DlRoundRect& rrect); + void PushPlatformViewClipRSuperellipse(const DlRoundSuperellipse& rse); + void PushPlatformViewClipPath(const DlPath& path); // Removes the `Mutator` on the top of the stack // and destroys it. @@ -249,6 +324,22 @@ class EmbeddedViewParams { mutators_stack_.PushBackdropFilter(filter, filter_rect); } + void PushPlatformViewClipRect(const DlRect& clip_rect) { + mutators_stack_.PushPlatformViewClipRect(clip_rect); + } + + void PushPlatformViewClipRRect(const DlRoundRect& clip_rrect) { + mutators_stack_.PushPlatformViewClipRRect(clip_rrect); + } + + void PushPlatformViewClipRSuperellipse(const DlRoundSuperellipse& clip_rse) { + mutators_stack_.PushPlatformViewClipRSuperellipse(clip_rse); + } + + void PushPlatformViewClipPath(const DlPath& clip_path) { + mutators_stack_.PushPlatformViewClipPath(clip_path); + } + bool operator==(const EmbeddedViewParams& other) const { return size_points_ == other.size_points_ && mutators_stack_ == other.mutators_stack_ && @@ -459,6 +550,13 @@ class ExternalViewEmbedder { const std::shared_ptr& filter, const DlRect& filter_rect) {} + virtual void PushClipRectToVisitedPlatformViews(const DlRect& clip_rect) {} + virtual void PushClipRRectToVisitedPlatformViews( + const DlRoundRect& clip_rrect) {} + virtual void PushClipRSuperellipseToVisitedPlatformViews( + const DlRoundSuperellipse& clip_rse) {} + virtual void PushClipPathToVisitedPlatformViews(const DlPath& clip_path) {} + private: bool used_this_frame_ = false; diff --git a/engine/src/flutter/flow/layers/clip_path_layer.cc b/engine/src/flutter/flow/layers/clip_path_layer.cc index d2006cb25ce..dd578ccd7bf 100644 --- a/engine/src/flutter/flow/layers/clip_path_layer.cc +++ b/engine/src/flutter/flow/layers/clip_path_layer.cc @@ -31,4 +31,9 @@ void ClipPathLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { } } +void ClipPathLayer::PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const { + view_embedder->PushClipPathToVisitedPlatformViews(clip_shape()); +} + } // namespace flutter diff --git a/engine/src/flutter/flow/layers/clip_path_layer.h b/engine/src/flutter/flow/layers/clip_path_layer.h index 5ac6de72539..30f6225cd3a 100644 --- a/engine/src/flutter/flow/layers/clip_path_layer.h +++ b/engine/src/flutter/flow/layers/clip_path_layer.h @@ -20,6 +20,8 @@ class ClipPathLayer : public ClipShapeLayer { const DlRect clip_shape_bounds() const override; void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; + void PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipPathLayer); diff --git a/engine/src/flutter/flow/layers/clip_rect_layer.cc b/engine/src/flutter/flow/layers/clip_rect_layer.cc index f67518696a0..92f93c5d860 100644 --- a/engine/src/flutter/flow/layers/clip_rect_layer.cc +++ b/engine/src/flutter/flow/layers/clip_rect_layer.cc @@ -17,4 +17,9 @@ void ClipRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { mutator.clipRect(clip_shape(), clip_behavior() != Clip::kHardEdge); } +void ClipRectLayer::PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const { + view_embedder->PushClipRectToVisitedPlatformViews(clip_shape()); +} + } // namespace flutter diff --git a/engine/src/flutter/flow/layers/clip_rect_layer.h b/engine/src/flutter/flow/layers/clip_rect_layer.h index b35401a3214..a59fe28ca5c 100644 --- a/engine/src/flutter/flow/layers/clip_rect_layer.h +++ b/engine/src/flutter/flow/layers/clip_rect_layer.h @@ -17,6 +17,8 @@ class ClipRectLayer : public ClipShapeLayer { const DlRect clip_shape_bounds() const override; void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; + void PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipRectLayer); diff --git a/engine/src/flutter/flow/layers/clip_rrect_layer.cc b/engine/src/flutter/flow/layers/clip_rrect_layer.cc index 6b172d50c58..87aead37124 100644 --- a/engine/src/flutter/flow/layers/clip_rrect_layer.cc +++ b/engine/src/flutter/flow/layers/clip_rrect_layer.cc @@ -23,4 +23,9 @@ void ClipRRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const { } } +void ClipRRectLayer::PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const { + view_embedder->PushClipRRectToVisitedPlatformViews(clip_shape()); +} + } // namespace flutter diff --git a/engine/src/flutter/flow/layers/clip_rrect_layer.h b/engine/src/flutter/flow/layers/clip_rrect_layer.h index 091eaf0e0f7..92b8889fd4b 100644 --- a/engine/src/flutter/flow/layers/clip_rrect_layer.h +++ b/engine/src/flutter/flow/layers/clip_rrect_layer.h @@ -17,6 +17,8 @@ class ClipRRectLayer : public ClipShapeLayer { const DlRect clip_shape_bounds() const override; void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; + void PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipRRectLayer); diff --git a/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc b/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc index d7f05013958..ef4e8d55ec5 100644 --- a/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc +++ b/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc @@ -20,4 +20,9 @@ void ClipRSuperellipseLayer::ApplyClip( mutator.clipRSuperellipse(clip_shape(), clip_behavior() != Clip::kHardEdge); } +void ClipRSuperellipseLayer::PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const { + view_embedder->PushClipRSuperellipseToVisitedPlatformViews(clip_shape()); +} + } // namespace flutter diff --git a/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h b/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h index 8f1ba25f614..47577930b45 100644 --- a/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h +++ b/engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h @@ -18,6 +18,8 @@ class ClipRSuperellipseLayer : public ClipShapeLayer { const DlRect clip_shape_bounds() const override; void ApplyClip(LayerStateStack::MutatorContext& mutator) const override; + void PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const override; private: FML_DISALLOW_COPY_AND_ASSIGN(ClipRSuperellipseLayer); diff --git a/engine/src/flutter/flow/layers/clip_shape_layer.h b/engine/src/flutter/flow/layers/clip_shape_layer.h index d2baa33b427..220f05c802e 100644 --- a/engine/src/flutter/flow/layers/clip_shape_layer.h +++ b/engine/src/flutter/flow/layers/clip_shape_layer.h @@ -56,6 +56,11 @@ class ClipShapeLayer : public CacheableContainerLayer { Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); + ExternalViewEmbedder* view_embedder = context->view_embedder; + if (view_embedder) { + PushClipToEmbeddedNativeViewMutatorStack(view_embedder); + } + auto mutator = context->state_stack.save(); ApplyClip(mutator); @@ -107,6 +112,8 @@ class ClipShapeLayer : public CacheableContainerLayer { protected: virtual const DlRect clip_shape_bounds() const = 0; virtual void ApplyClip(LayerStateStack::MutatorContext& mutator) const = 0; + virtual void PushClipToEmbeddedNativeViewMutatorStack( + ExternalViewEmbedder* view_embedder) const = 0; virtual ~ClipShapeLayer() = default; const ClipShape& clip_shape() const { return clip_shape_; } diff --git a/engine/src/flutter/flow/mutators_stack_unittests.cc b/engine/src/flutter/flow/mutators_stack_unittests.cc index 7944d74cf12..d21e0c3df32 100644 --- a/engine/src/flutter/flow/mutators_stack_unittests.cc +++ b/engine/src/flutter/flow/mutators_stack_unittests.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "display_list/geometry/dl_geometry_types.h" #include "flutter/display_list/effects/dl_image_filters.h" #include "flutter/flow/embedded_views.h" #include "gtest/gtest.h" @@ -98,6 +99,43 @@ TEST(MutatorsStack, PushOpacity) { ASSERT_TRUE(iter->get()->GetAlpha() == 240); } +TEST(MutatorsStack, PushPlatformViewClipRect) { + MutatorsStack stack; + auto rect = DlRect(); + stack.PushPlatformViewClipRect(rect); + auto iter = stack.Bottom(); + ASSERT_TRUE(iter->get()->GetType() == MutatorType::kBackdropClipRect); + ASSERT_TRUE(iter->get()->GetBackdropClipRect().rect == rect); +} + +TEST(MutatorsStack, PushPlatformViewClipRRect) { + MutatorsStack stack; + auto rrect = DlRoundRect(); + stack.PushPlatformViewClipRRect(rrect); + auto iter = stack.Bottom(); + ASSERT_TRUE(iter->get()->GetType() == MutatorType::kBackdropClipRRect); + ASSERT_TRUE(iter->get()->GetBackdropClipRRect().rrect == rrect); +} + +TEST(MutatorsStack, PushPlatformViewClipRSuperellipse) { + MutatorsStack stack; + auto rse = DlRoundSuperellipse(); + stack.PushPlatformViewClipRSuperellipse(rse); + auto iter = stack.Bottom(); + ASSERT_TRUE(iter->get()->GetType() == + MutatorType::kBackdropClipRSuperellipse); + ASSERT_TRUE(iter->get()->GetBackdropClipRSuperellipse().rse == rse); +} + +TEST(MutatorsStack, PushPlatformViewClipPath) { + MutatorsStack stack; + auto path = DlPath(); + stack.PushPlatformViewClipPath(path); + auto iter = stack.Bottom(); + ASSERT_TRUE(iter->get()->GetType() == MutatorType::kBackdropClipPath); + ASSERT_TRUE(iter->get()->GetBackdropClipPath().path == path); +} + TEST(MutatorsStack, PushBackdropFilter) { MutatorsStack stack; const int num_of_mutators = 10; diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index ed3324fefb8..2b3340d5b4e 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -1785,6 +1785,10 @@ void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView( case MutatorType::kClipPath: case MutatorType::kOpacity: case MutatorType::kBackdropFilter: + case MutatorType::kBackdropClipRect: + case MutatorType::kBackdropClipRRect: + case MutatorType::kBackdropClipRSuperellipse: + case MutatorType::kBackdropClipPath: break; } ++iter; @@ -2294,6 +2298,10 @@ void PlatformViewAndroidJNIImpl::onDisplayPlatformView2( // TODO(cyanglaz): Implement other mutators. // https://github.com/flutter/flutter/issues/58426 case MutatorType::kBackdropFilter: + case MutatorType::kBackdropClipRect: + case MutatorType::kBackdropClipRRect: + case MutatorType::kBackdropClipRSuperellipse: + case MutatorType::kBackdropClipPath: break; } ++iter; 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 4055681a9c5..7efa0d11831 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 @@ -94,10 +94,12 @@ static BOOL _preparedOnce = NO; - (instancetype)initWithFrame:(CGRect)frame blurRadius:(CGFloat)blurRadius + cornerRadius:(CGFloat)cornerRadius visualEffectView:(UIVisualEffectView*)visualEffectView { if (self = [super init]) { _frame = frame; _blurRadius = blurRadius; + _cornerRadius = cornerRadius; [PlatformViewFilter prepareOnce:visualEffectView]; if (![PlatformViewFilter isUIVisualEffectViewImplementationValid]) { FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to " @@ -163,6 +165,9 @@ static BOOL _preparedOnce = NO; visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor; visualEffectView.frame = _frame; + visualEffectView.layer.cornerRadius = _cornerRadius; + visualEffectView.clipsToBounds = YES; + self.backdropFilterView = visualEffectView; } @@ -831,3 +836,6 @@ static BOOL _preparedOnce = NO; return YES; } @end + +@implementation PendingRRectClip +@end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h index bb90c1aa954..82a3718f182 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h @@ -114,6 +114,22 @@ NS_ASSUME_NONNULL_BEGIN /// @brief Pushes the view id of a visted platform view to the list of visied platform views. - (void)pushVisitedPlatformViewId:(int64_t)viewId; +/// @brief Pushes the outstanding rectangular clips to the mutator stack of each visited platform +/// view +- (void)pushClipRectToVisitedPlatformViews:(const flutter::DlRect&)clipRect; + +/// @brief Pushes the outstanding rounded rectangular clips to the mutator stack of each visited +/// platform view +- (void)pushClipRRectToVisitedPlatformViews:(const flutter::DlRoundRect&)clipRRect; + +/// @brief Pushes the outstanding round super elliptical clips to the mutator stack of each visited +/// platform view +- (void)pushClipRSuperellipseToVisitedPlatformViews:(const flutter::DlRoundSuperellipse&)clipRse; + +/// @brief Pushes the outstanding path clips to the mutator stack of each visited platform +/// view +- (void)pushClipPathToVisitedPlatformViews:(const flutter::DlPath&)clipPath; + @end @interface FlutterPlatformViewsController (Testing) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm index 4dfc483b46b..2ff23159517 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm @@ -3,6 +3,8 @@ // found in the LICENSE file. #import "shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.h" +#include "display_list/geometry/dl_geometry_types.h" +#include "impeller/geometry/rounding_radii.h" #include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h" #include "flutter/display_list/utils/dl_matrix_clip_tracker.h" @@ -525,6 +527,8 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { DlMatrix transformMatrix; NSMutableArray* blurFilters = [[NSMutableArray alloc] init]; + NSMutableArray* pendingClipRRects = [[NSMutableArray alloc] init]; + FML_DCHECK(!clipView.maskView || [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]); if (clipView.maskView) { @@ -605,8 +609,19 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x(); UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; + + // TODO(https://github.com/flutter/flutter/issues/179126) + CGFloat cornerRadius = 0.0; + if ([pendingClipRRects count] > 0) { + cornerRadius = pendingClipRRects[0].topLeftRadius; + [pendingClipRRects removeAllObjects]; + } + visualEffectView.layer.cornerRadius = cornerRadius; + visualEffectView.clipsToBounds = YES; + PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView blurRadius:blurRadius + cornerRadius:cornerRadius visualEffectView:visualEffectView]; if (!filter) { self.canApplyBlurBackdrop = NO; @@ -615,6 +630,32 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { } break; } + case flutter::MutatorType::kBackdropClipRect: { + // The frame already handles cropping into the rect so this can + // no-op + break; + } + case flutter::MutatorType::kBackdropClipRRect: { + PendingRRectClip* clip = [[PendingRRectClip alloc] init]; + DlRoundRect rrect = (*iter)->GetBackdropClipRRect().rrect; + + clip.rect = boundingRect; + impeller::RoundingRadii radii = rrect.GetRadii(); + clip.topLeftRadius = radii.top_left.width; + clip.topRightRadius = radii.top_right.width; + clip.bottomLeftRadius = radii.bottom_left.width; + clip.bottomRightRadius = radii.bottom_right.width; + [pendingClipRRects addObject:clip]; + break; + } + case flutter::MutatorType::kBackdropClipRSuperellipse: { + // TODO(https://github.com/flutter/flutter/issues/179125) + break; + } + case flutter::MutatorType::kBackdropClipPath: { + // TODO(https://github.com/flutter/flutter/issues/179127) + break; + } } ++iter; } @@ -1001,6 +1042,38 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { self.visitedPlatformViews.push_back(viewId); } +- (void)pushClipRectToVisitedPlatformViews:(const flutter::DlRect&)clipRect { + for (int64_t id : self.visitedPlatformViews) { + flutter::EmbeddedViewParams params = self.currentCompositionParams[id]; + params.PushPlatformViewClipRect(clipRect); + self.currentCompositionParams[id] = params; + } +} + +- (void)pushClipRRectToVisitedPlatformViews:(const flutter::DlRoundRect&)clipRRect { + for (int64_t id : self.visitedPlatformViews) { + flutter::EmbeddedViewParams params = self.currentCompositionParams[id]; + params.PushPlatformViewClipRRect(clipRRect); + self.currentCompositionParams[id] = params; + } +} + +- (void)pushClipRSuperellipseToVisitedPlatformViews:(const flutter::DlRoundSuperellipse&)clipRse { + for (int64_t id : self.visitedPlatformViews) { + flutter::EmbeddedViewParams params = self.currentCompositionParams[id]; + params.PushPlatformViewClipRSuperellipse(clipRse); + self.currentCompositionParams[id] = params; + } +} + +- (void)pushClipPathToVisitedPlatformViews:(const flutter::DlPath&)clipPath { + for (int64_t id : self.visitedPlatformViews) { + flutter::EmbeddedViewParams params = self.currentCompositionParams[id]; + params.PushPlatformViewClipPath(clipPath); + self.currentCompositionParams[id] = params; + } +} + - (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId { return self.currentCompositionParams.find(viewId)->second; } 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 75376a4560c..2ef07694ac8 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 @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "display_list/geometry/dl_geometry_types.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import @@ -432,6 +433,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter1 = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:visualEffectView1]; [clippingView applyBlurBackdropFilters:@[ platformViewFilter1 ]]; @@ -443,6 +445,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter2 = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:visualEffectView2]; [clippingView applyBlurBackdropFilters:@[ platformViewFilter2 ]]; @@ -1545,6 +1548,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:visualEffectView]; XCTAssertNotNil(platformViewFilter); } @@ -1555,6 +1559,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:visualEffectView]; XCTAssertNil(platformViewFilter); } @@ -1578,6 +1583,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:editedUIVisualEffectView]; XCTAssertNil(platformViewFilter); } @@ -1602,10 +1608,92 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:editedUIVisualEffectView]; XCTAssertNil(platformViewFilter); } +- (void)testApplyBackdropFilterRespectsClipRRect { + flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate; + + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/GetDefaultTaskRunner(), + /*raster=*/GetDefaultTaskRunner(), + /*ui=*/GetDefaultTaskRunner(), + /*io=*/GetDefaultTaskRunner()); + FlutterPlatformViewsController* flutterPlatformViewsController = + [[FlutterPlatformViewsController alloc] init]; + flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner(); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/flutter::IOSRenderingAPI::kMetal, + /*platform_views_controller=*/flutterPlatformViewsController, + /*task_runners=*/runners, + /*worker_task_runner=*/nil, + /*is_gpu_disabled_jsync_switch=*/std::make_shared()); + + FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = + [[FlutterPlatformViewsTestMockFlutterPlatformFactory alloc] init]; + [flutterPlatformViewsController + registerViewFactory:factory + withId:@"MockFlutterPlatformView" + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + FlutterResult result = ^(id result) { + }; + [flutterPlatformViewsController + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create" + arguments:@{ + @"id" : @2, + @"viewType" : @"MockFlutterPlatformView" + }] + result:result]; + + XCTAssertNotNil(gMockPlatformView); + + UIView* flutterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; + flutterPlatformViewsController.flutterView = flutterView; + // Create embedded view params + flutter::MutatorsStack stack; + // Layer tree always pushes a screen scale factor to the stack + flutter::DlScalar screenScale = [UIScreen mainScreen].scale; + flutter::DlMatrix screenScaleMatrix = flutter::DlMatrix::MakeScale({screenScale, screenScale, 1}); + stack.PushTransform(screenScaleMatrix); + + // Push a rounded rect clip + auto clipRect = flutter::DlRect::MakeXYWH(2, 2, 6, 6); + auto clipRRect = flutter::DlRoundRect::MakeRectXY(clipRect, 3, 3); + stack.PushPlatformViewClipRRect(clipRRect); + + // Push a backdrop filter + auto filter = flutter::DlBlurImageFilter::Make(5, 2, flutter::DlTileMode::kClamp); + stack.PushBackdropFilter(filter, + flutter::DlRect::MakeXYWH(0, 0, screenScale * 10, screenScale * 10)); + + auto embeddedViewParams = std::make_unique( + screenScaleMatrix, flutter::DlSize(10, 10), stack); + + [flutterPlatformViewsController prerollCompositeEmbeddedView:2 + withParams:std::move(embeddedViewParams)]; + [flutterPlatformViewsController + compositeView:2 + withParams:[flutterPlatformViewsController compositionParamsForView:2]]; + + XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); + ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; + [flutterView addSubview:childClippingView]; + + [flutterView setNeedsLayout]; + [flutterView layoutIfNeeded]; + + NSArray* filters = childClippingView.backdropFilterSubviews; + XCTAssertEqual(filters.count, 1u); + + UIVisualEffectView* visualEffectView = filters[0]; + auto radii = clipRRect.GetRadii(); + + XCTAssertEqual(visualEffectView.layer.cornerRadius, radii.top_left.width); +} + - (void)testBackdropFilterVisualEffectSubviewBackgroundColor { __weak UIVisualEffectView* weakVisualEffectView; @@ -1616,6 +1704,7 @@ fml::RefPtr GetDefaultTaskRunner() { PlatformViewFilter* platformViewFilter = [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) blurRadius:5 + cornerRadius:0 visualEffectView:visualEffectView]; CGColorRef visualEffectSubviewBackgroundColor = nil; for (UIView* view in [platformViewFilter backdropFilterView].subviews) { 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 649b4d28804..3009d1f28e7 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 @@ -94,6 +94,9 @@ // The inputRadius can be customized and it doesn't add any color saturation to the blurred view. @property(nonatomic, readonly) UIVisualEffectView* backdropFilterView; +// Determines the corner radius of the backdrop filter view. +@property(nonatomic, readonly) CGFloat cornerRadius; + // For testing only. + (void)resetPreparation; @@ -111,6 +114,7 @@ // implementation in `PlatformViewFilter`, this method will return nil. - (instancetype)initWithFrame:(CGRect)frame blurRadius:(CGFloat)blurRadius + cornerRadius:(CGFloat)cornerRadius visualEffectView:(UIVisualEffectView*)visualEffectView NS_DESIGNATED_INITIALIZER; @end @@ -195,4 +199,12 @@ - (ForwardingGestureRecognizer*)recreateRecognizerWithTarget:(id)target; @end +@interface PendingRRectClip : NSObject +@property(nonatomic) flutter::DlRect rect; +@property(nonatomic) CGFloat topLeftRadius; +@property(nonatomic) CGFloat topRightRadius; +@property(nonatomic) CGFloat bottomRightRadius; +@property(nonatomic) CGFloat bottomLeftRadius; +@end + #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ 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 188547f89e3..066bc88b078 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 @@ -71,6 +71,20 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder { const std::shared_ptr& filter, const DlRect& filter_rect) override; + // |ExternalViewEmbedder| + void PushClipRectToVisitedPlatformViews(const DlRect& clip_rect) override; + + // |ExternalViewEmbedder| + void PushClipRRectToVisitedPlatformViews( + const DlRoundRect& clip_rrect) override; + + // |ExternalViewEmbedder| + void PushClipRSuperellipseToVisitedPlatformViews( + const DlRoundSuperellipse& clip_rse) override; + + // |ExternalViewEmbedder| + void PushClipPathToVisitedPlatformViews(const DlPath& clip_path) 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 6d2fbfcfaa3..6e46d890ff0 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 @@ -113,4 +113,25 @@ void IOSExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) { [platform_views_controller_ pushVisitedPlatformViewId:view_id]; } +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::PushClipRectToVisitedPlatformViews(const DlRect& clip_rect) { + [platform_views_controller_ pushClipRectToVisitedPlatformViews:clip_rect]; +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::PushClipRRectToVisitedPlatformViews(const DlRoundRect& clip_rrect) { + [platform_views_controller_ pushClipRRectToVisitedPlatformViews:clip_rrect]; +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::PushClipRSuperellipseToVisitedPlatformViews( + const DlRoundSuperellipse& clip_rse) { + [platform_views_controller_ pushClipRSuperellipseToVisitedPlatformViews:clip_rse]; +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::PushClipPathToVisitedPlatformViews(const DlPath& clip_path) { + [platform_views_controller_ pushClipPathToVisitedPlatformViews:clip_path]; +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc index f4ef3e75fcc..72674a38e07 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_view_embedder.cc @@ -177,6 +177,10 @@ struct PlatformView { } case MutatorType::kOpacity: case MutatorType::kBackdropFilter: + case MutatorType::kBackdropClipRect: + case MutatorType::kBackdropClipRRect: + case MutatorType::kBackdropClipRSuperellipse: + case MutatorType::kBackdropClipPath: break; } } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_layers.cc b/engine/src/flutter/shell/platform/embedder/embedder_layers.cc index 4c648288785..b671e860b59 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_layers.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_layers.cc @@ -186,6 +186,10 @@ void EmbedderLayers::PushPlatformViewLayer( } } break; case MutatorType::kBackdropFilter: + case MutatorType::kBackdropClipRect: + case MutatorType::kBackdropClipRRect: + case MutatorType::kBackdropClipRSuperellipse: + case MutatorType::kBackdropClipPath: break; } }