mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
4966a7d100
commit
5c08658ec2
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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_; }
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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_
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,6 +186,10 @@ void EmbedderLayers::PushPlatformViewLayer(
|
||||
}
|
||||
} break;
|
||||
case MutatorType::kBackdropFilter:
|
||||
case MutatorType::kBackdropClipRect:
|
||||
case MutatorType::kBackdropClipRRect:
|
||||
case MutatorType::kBackdropClipRSuperellipse:
|
||||
case MutatorType::kBackdropClipPath:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user