Platform view blur clipping - Rounded Rect (iOS) (#177551)

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

This PR aims to fix blurs on platform views not being clipped on ios.
Currently the clips were not forwarded to the mutator stack and thus
just ignored. The frame the blur is in was being used which would always
behave like a clip rect ignoring any corner radii or clip paths etc

Now all rrect, rse and path clips are being forwarded to the mutator
stack.

**Currently only rounded rect has been implemented though, and even in
that just a single uniform corner radii, which seems to be most of the
use cases from users who have run into this issue.**


Example: 
```dart
Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(50),
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          color: Colors.black.withOpacity(0.2),
          borderRadius: BorderRadius.circular(500),
        ),
        child: BackdropFilter(
          filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
          child: const Center(
            child: Text('Blurred block 1'),
          ),
        ),
      ),
    );
```
Before:
<img
src="https://github.com/user-attachments/assets/229c82a0-70de-4e23-841e-2cbea92cc79f"
height="600">

After:
<img
src="https://github.com/user-attachments/assets/537f4215-5f0f-4cd6-aa09-e058d133e8ac"
height="600">


*List which issues are fixed by this PR. You must list at least one
issue. An issue is not required if the PR fixes something trivial like a
typo.*
https://github.com/flutter/flutter/issues/115926

*If you had to change anything in the [flutter/tests] repo, include a
link to the migration guide as per the [breaking change policy].*

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Gray Mackall <34871572+gmackall@users.noreply.github.com>
This commit is contained in:
Hari07 2025-12-17 01:04:39 +05:30 committed by GitHub
parent 4966a7d100
commit 5c08658ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 444 additions and 2 deletions

View File

@ -96,6 +96,28 @@ void MutatorsStack::PushBackdropFilter(
vector_.push_back(element);
}
void MutatorsStack::PushPlatformViewClipRect(const DlRect& rect) {
std::shared_ptr<Mutator> element =
std::make_shared<Mutator>(BackdropClipRect(rect));
vector_.push_back(element);
}
void MutatorsStack::PushPlatformViewClipRRect(const DlRoundRect& rrect) {
std::shared_ptr<Mutator> element =
std::make_shared<Mutator>(BackdropClipRRect(rrect));
vector_.push_back(element);
}
void MutatorsStack::PushPlatformViewClipRSuperellipse(
const DlRoundSuperellipse& rse) {
std::shared_ptr<Mutator> element =
std::make_shared<Mutator>(BackdropClipRSuperellipse(rse));
vector_.push_back(element);
}
void MutatorsStack::PushPlatformViewClipPath(const DlPath& path) {
std::shared_ptr<Mutator> element =
std::make_shared<Mutator>(BackdropClipPath(path));
vector_.push_back(element);
}
void MutatorsStack::Pop() {
vector_.pop_back();
}

View File

@ -10,6 +10,8 @@
#include <variant>
#include <vector>
#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<MutatorType>(data_.index());
}
@ -98,6 +149,18 @@ class Mutator {
const ImageFilterMutation& GetFilterMutation() const {
return std::get<ImageFilterMutation>(data_);
}
const BackdropClipRect& GetBackdropClipRect() const {
return std::get<BackdropClipRect>(data_);
}
const BackdropClipRRect& GetBackdropClipRRect() const {
return std::get<BackdropClipRRect>(data_);
}
const BackdropClipRSuperellipse& GetBackdropClipRSuperellipse() const {
return std::get<BackdropClipRSuperellipse>(data_);
}
const BackdropClipPath& GetBackdropClipPath() const {
return std::get<BackdropClipPath>(data_);
}
const uint8_t& GetAlpha() const { return std::get<uint8_t>(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<DlImageFilter>& 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<DlImageFilter>& 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;

View File

@ -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

View File

@ -20,6 +20,8 @@ class ClipPathLayer : public ClipShapeLayer<DlPath> {
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);

View File

@ -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

View File

@ -17,6 +17,8 @@ class ClipRectLayer : public ClipShapeLayer<DlRect> {
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);

View File

@ -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

View File

@ -17,6 +17,8 @@ class ClipRRectLayer : public ClipShapeLayer<DlRoundRect> {
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);

View File

@ -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

View File

@ -18,6 +18,8 @@ class ClipRSuperellipseLayer : public ClipShapeLayer<DlRoundSuperellipse> {
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);

View File

@ -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_; }

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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<PendingRRectClip*>* 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;
}

View File

@ -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 <OCMock/OCMock.h>
@ -432,6 +433,7 @@ fml::RefPtr<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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<fml::TaskRunner> 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<flutter::PlatformViewIOS>(
/*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<fml::SyncSwitch>());
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<flutter::EmbeddedViewParams>(
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<UIVisualEffectView*>* 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<fml::TaskRunner> 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) {

View File

@ -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_

View File

@ -71,6 +71,20 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder {
const std::shared_ptr<DlImageFilter>& 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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -186,6 +186,10 @@ void EmbedderLayers::PushPlatformViewLayer(
}
} break;
case MutatorType::kBackdropFilter:
case MutatorType::kBackdropClipRect:
case MutatorType::kBackdropClipRRect:
case MutatorType::kBackdropClipRSuperellipse:
case MutatorType::kBackdropClipPath:
break;
}
}