7#import <WebKit/WebKit.h>
17 return CGRectMake(clipDlRect.
GetX(),
24 CATransform3D
transform = CATransform3DIdentity;
50 CGPathMoveToPoint(path_ref_, nil,
p2.x,
p2.y);
53 CGPathAddLineToPoint(path_ref_, nil,
p2.x,
p2.y);
56 CGPathAddQuadCurveToPoint(path_ref_, nil, cp.
x, cp.
y,
p2.x,
p2.y);
62 CGPathAddCurveToPoint(path_ref_, nil,
63 cp1.
x, cp1.
y, cp2.
x, cp2.
y,
p2.x,
p2.y);
65 void Close()
override { CGPathCloseSubpath(path_ref_); }
67 CGMutablePathRef
TakePath()
const {
return path_ref_; }
70 CGMutablePathRef path_ref_ = CGPathCreateMutable();
77@property(nonatomic)
BOOL backdropFilterViewConfigured;
82- (void)updateVisualEffectView:(UIVisualEffectView*)visualEffectView;
96 blurRadius:(CGFloat)blurRadius
97 cornerRadius:(CGFloat)cornerRadius
98 isRoundedSuperellipse:(
BOOL)isRoundedSuperellipse
99 visualEffectView:(UIVisualEffectView*)visualEffectView {
100 if (
self = [super init]) {
107 FML_DLOG(ERROR) <<
"Apple's API for UIVisualEffectView changed. Update the implementation to "
108 "access the gaussianBlur CAFilter.";
111 _backdropFilterView = visualEffectView;
112 _backdropFilterViewConfigured = NO;
124+ (void)prepareOnce:(UIVisualEffectView*)visualEffectView {
128 for (NSUInteger
i = 0;
i < visualEffectView.subviews.count;
i++) {
129 UIView*
view = visualEffectView.subviews[i];
130 if ([NSStringFromClass([view
class]) hasSuffix:
@"BackdropView"]) {
132 for (NSObject* filter in
view.layer.filters) {
133 if ([[filter valueForKey:
@"name"] isEqual:
@"gaussianBlur"] &&
134 [[filter valueForKey:
@"inputRadius"] isKindOfClass:[NSNumber class]]) {
139 }
else if ([NSStringFromClass([view
class]) hasSuffix:
@"VisualEffectSubview"]) {
146+ (
BOOL)isUIVisualEffectViewImplementationValid {
152 if (!
self.backdropFilterViewConfigured) {
153 [
self updateVisualEffectView:_backdropFilterView];
154 self.backdropFilterViewConfigured = YES;
156 return _backdropFilterView;
159- (void)updateVisualEffectView:(UIVisualEffectView*)visualEffectView {
160 NSObject* gaussianBlurFilter = [_gaussianBlurFilter copy];
162 UIView* backdropView = visualEffectView.subviews[_indexOfBackdropView];
163 [gaussianBlurFilter setValue:@(_blurRadius) forKey:@"inputRadius"];
164 backdropView.layer.filters = @[ gaussianBlurFilter ];
166 UIView* visualEffectSubview = visualEffectView.subviews[_indexOfVisualEffectSubview];
167 visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor;
168 visualEffectView.frame = _frame;
170 visualEffectView.layer.cornerRadius = _cornerRadius;
171 if (@available(iOS 13.0, *)) {
172 visualEffectView.layer.cornerCurve =
173 _isRoundedSuperellipse ? kCACornerCurveContinuous : kCACornerCurveCircular;
175 visualEffectView.clipsToBounds = YES;
177 self.backdropFilterView = visualEffectView;
184@property(nonatomic, copy) NSArray<PlatformViewFilter*>* filters;
194- (
BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
195 for (UIView*
view in
self.subviews) {
196 if ([
view pointInside:[
self convertPoint:point toView:view] withEvent:event]) {
205 if (
self.filters.count == 0 && filters.count == 0) {
208 self.filters = filters;
209 NSUInteger index = 0;
210 for (index = 0; index <
self.filters.count; index++) {
211 UIVisualEffectView* backdropFilterView;
213 if (
self.backdropFilterSubviews.count <= index) {
214 backdropFilterView = filter.backdropFilterView;
215 [self addSubview:backdropFilterView];
216 [self.backdropFilterSubviews addObject:backdropFilterView];
218 [filter updateVisualEffectView:self.backdropFilterSubviews[index]];
221 for (NSUInteger
i =
self.backdropFilterSubviews.count;
i > index;
i--) {
222 [
self.backdropFilterSubviews[i - 1] removeFromSuperview];
223 [
self.backdropFilterSubviews removeLastObject];
228 if (!_backdropFilterSubviews) {
229 _backdropFilterSubviews = [[NSMutableArray alloc] init];
231 return _backdropFilterSubviews;
246@property(nonatomic) CATransform3D reverseScreenScale;
248- (
fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
253 std::vector<fml::CFRef<CGPathRef>> paths_;
259 return [
self initWithFrame:frame screenScale:[UIScreen mainScreen].scale];
262- (instancetype)
initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale {
264 self.backgroundColor = UIColor.clearColor;
265 _reverseScreenScale = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
273 return [CAShapeLayer class];
276- (CAShapeLayer*)shapeLayer {
277 return (CAShapeLayer*)
self.layer;
284 [
self shapeLayer].path = nil;
285 [
self setNeedsDisplay];
293- (
BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
297- (void)drawRect:(CGRect)rect {
301 CGContextRef context = UIGraphicsGetCurrentContext();
302 CGContextSaveGState(context);
305 CGContextSetAlpha(context, 1);
307 for (
size_t i = 0;
i < paths_.size();
i++) {
308 CGContextAddPath(context, paths_.at(
i));
309 CGContextClip(context);
311 CGContextFillRect(context, rect);
312 CGContextRestoreGState(context);
316 [
super drawRect:rect];
317 if (![
self shapeLayer].path) {
318 if (paths_.size() == 1) {
320 [
self shapeLayer].path = paths_.at(0);
323 CGPathRef pathSoFar = CGPathCreateWithRect(
rectSoFar_, nil);
324 [
self shapeLayer].path = pathSoFar;
325 CGPathRelease(pathSoFar);
333 CGPathRef
path = CGPathCreateWithRect(clipRect, nil);
335 CATransform3D matrixInPoints =
337 paths_.push_back([
self getTransformedPath:
path matrix:matrixInPoints]);
338 CGAffineTransform affine = [
self affineWithMatrix:matrixInPoints];
340 if (affine.b == 0 && affine.c == 0) {
348 if (clipDlRRect.IsEmpty()) {
350 }
else if (clipDlRRect.IsRect()) {
351 [
self clipRect:clipDlRRect.GetBounds() matrix:matrix];
354 CGPathRef pathRef =
nullptr;
357 if (clipDlRRect.GetRadii().AreAllCornersSame()) {
359 auto radii = clipDlRRect.GetRadii();
361 CGPathCreateWithRoundedRect(clipRect, radii.top_left.width, radii.top_left.height, nil);
363 CGMutablePathRef mutablePathRef = CGPathCreateMutable();
367 auto top = clipDlRect.
GetTop();
381 CGPathMoveToPoint(mutablePathRef, nil,
382 left + top_left.width, top);
384 CGPathAddLineToPoint(mutablePathRef, nil,
385 right - top_right.width, top);
386 CGPathAddCurveToPoint(mutablePathRef, nil,
388 right, top + top_right.height,
389 right, top + top_right.height);
391 CGPathAddLineToPoint(mutablePathRef, nil,
392 right, bottom - bottom_right.height);
393 CGPathAddCurveToPoint(mutablePathRef, nil,
395 right - bottom_right.width, bottom,
396 right - bottom_right.width, bottom);
398 CGPathAddLineToPoint(mutablePathRef, nil,
399 left + bottom_left.width, bottom);
400 CGPathAddCurveToPoint(mutablePathRef, nil,
402 left, bottom - bottom_left.height,
403 left, bottom - bottom_left.height);
405 CGPathAddLineToPoint(mutablePathRef, nil,
406 left, top + top_left.height);
407 CGPathAddCurveToPoint(mutablePathRef, nil,
409 left + top_left.width, top,
410 left + top_left.width, top);
411 CGPathCloseSubpath(mutablePathRef);
412 pathRef = mutablePathRef;
415 CATransform3D matrixInPoints =
420 paths_.push_back([
self getTransformedPath:pathRef matrix:matrixInPoints]);
427 CGPathReceiver receiver;
432 dlPath.Dispatch(receiver);
435 CATransform3D matrixInPoints =
437 paths_.push_back([
self getTransformedPath:receiver.TakePath() matrix:matrixInPoints]);
440- (CGAffineTransform)affineWithMatrix:(CATransform3D)matrix {
441 return CGAffineTransformMake(matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.m41,
445- (
fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {
446 CGAffineTransform affine = [
self affineWithMatrix:matrix];
447 CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &affine);
459@property(nonatomic) NSUInteger capacity;
463@property(nonatomic) NSMutableSet<FlutterClippingMaskView*>* pool;
469- (instancetype)initWithCapacity:(NSInteger)capacity {
470 if (
self = [super init]) {
473 _pool = [[NSMutableSet alloc] initWithCapacity:1];
474 _capacity = capacity;
481 if (
self.pool.count == 0) {
484 screenScale:UIScreen.mainScreen.scale];
487 maskView.frame = frame;
489 [
self.pool removeObject:maskView];
496 if (
self.pool.count ==
self.capacity) {
499 [
self.pool addObject:maskView];
504@implementation UIView (FirstResponder)
506 if (
self.isFirstResponder) {
509 for (UIView* subview in
self.subviews) {
510 if (subview.flt_hasFirstResponderInViewHierarchySubtree) {
519@property(nonatomic, weak, readonly) UIView* embeddedView;
520@property(nonatomic, weak, readonly) UIViewController<FlutterViewResponder>* flutterViewController;
526- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
528 gestureRecognizersBlockingPolicy:
530 self = [
super initWithFrame:embeddedView.frame];
532 self.multipleTouchEnabled = YES;
533 _embeddedView = embeddedView;
534 _platformViewsController = platformViewsController;
536 embeddedView.autoresizingMask =
537 (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
539 [
self addSubview:embeddedView];
543 platformViewsController:platformViewsController];
545 _delayingRecognizer =
548 forwardingRecognizer:forwardingRecognizer];
549 _blockingPolicy = blockingPolicy;
555 [
self addGestureRecognizer:_delayingRecognizer];
557 [
self addGestureRecognizer:forwardingRecognizer];
562- (void)forceResetForwardingGestureRecognizerState {
570 [oldForwardingRecognizer recreateRecognizerWithTarget:self];
571 self.delayingRecognizer.forwardingRecognizer = newForwardingRecognizer;
572 [
self removeGestureRecognizer:oldForwardingRecognizer];
573 [
self addGestureRecognizer:newForwardingRecognizer];
576- (void)releaseGesture {
577 self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
580- (
BOOL)containsWebView:(UIView*)view {
581 if ([view isKindOfClass:[WKWebView class]]) {
584 for (UIView* subview in
view.subviews) {
585 if ([
self containsWebView:subview]) {
592- (void)searchAndFixWebView:(UIView*)view {
593 if ([view isKindOfClass:[WKWebView class]]) {
594 return [
self searchAndFixWebViewGestureRecognzier:view];
596 for (UIView* subview in
view.subviews) {
597 [
self searchAndFixWebView:subview];
602- (void)searchAndFixWebViewGestureRecognzier:(UIView*)view {
603 for (UIGestureRecognizer* recognizer in
view.gestureRecognizers) {
614 if (recognizer.enabled &&
615 [NSStringFromClass([recognizer
class]) hasSuffix:
@"TouchEventsGestureRecognizer"]) {
616 recognizer.enabled = NO;
617 recognizer.enabled = YES;
620 for (UIView* subview in
view.subviews) {
621 [
self searchAndFixWebViewGestureRecognzier:subview];
625- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
628 if (
self.flutterViewController == nil) {
631 CGPoint pointInFlutterView = [
self convertPoint:point toView:self.flutterViewController.view];
636 if (![
self.flutterViewController
637 platformViewShouldAcceptTouchAtTouchBeganLocation:pointInFlutterView]) {
641 return [
super hitTest:point withEvent:event];
644- (void)blockGesture {
645 switch (_blockingPolicy) {
651 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
661 if (@available(iOS 26.0, *)) {
666 NSNumber* isWorkaroundDisabled =
667 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTDisableWebViewGestureReset"];
668 if (!isWorkaroundDisabled.boolValue) {
669 [
self searchAndFixWebView:self.embeddedView];
671 }
else if (@available(iOS 18.2, *)) {
675 if ([
self containsWebView:
self.embeddedView]) {
676 [
self removeGestureRecognizer:self.delayingRecognizer];
677 [
self addGestureRecognizer:self.delayingRecognizer];
683 if (
self.delayingRecognizer.touchedEndedWithoutBlocking) {
687 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
692 self.delayingRecognizer.shouldEndInNextTouchesEnded = YES;
703- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
706- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
709- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
712- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
715- (
id)accessibilityContainer {
716 return self.flutterAccessibilityContainer;
723- (instancetype)initWithTarget:(
id)target
725 forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
726 self = [
super initWithTarget:target action:action];
728 self.delaysTouchesBegan = YES;
729 self.delaysTouchesEnded = YES;
731 _shouldEndInNextTouchesEnded = NO;
732 _touchedEndedWithoutBlocking = NO;
733 _forwardingRecognizer = forwardingRecognizer;
738- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
739 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
742 return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer !=
self;
745- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
746 shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
747 return otherGestureRecognizer ==
self;
750- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
752 [
super touchesBegan:touches withEvent:event];
755- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
756 if (
self.shouldEndInNextTouchesEnded) {
757 self.state = UIGestureRecognizerStateEnded;
758 self.shouldEndInNextTouchesEnded = NO;
760 self.touchedEndedWithoutBlocking = YES;
762 [
super touchesEnded:touches withEvent:event];
765- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
766 self.state = UIGestureRecognizerStateFailed;
788- (instancetype)initWithTarget:(
id)target
790 self = [
super initWithTarget:target action:nil];
794 _platformViewsController = platformViewsController;
802 platformViewsController:_platformViewsController];
805- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
820 [_flutterViewController touchesBegan:touches withEvent:event];
824- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
825 [_flutterViewController touchesMoved:touches withEvent:event];
828- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
829 [_flutterViewController touchesEnded:touches withEvent:event];
836 self.state = UIGestureRecognizerStateFailed;
838 [
self forceResetStateIfNeeded];
842- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
848 [_flutterViewController forceTouchesCancelled:touches];
851 self.state = UIGestureRecognizerStateFailed;
853 [
self forceResetStateIfNeeded];
857- (void)forceResetStateIfNeeded {
861 if (@available(iOS 26.0, *)) {
865 dispatch_async(dispatch_get_main_queue(), ^{
870 if (strongSelf.state != UIGestureRecognizerStatePossible) {
871 [(FlutterTouchInterceptingView*)strongSelf.view forceResetForwardingGestureRecognizerState];
876- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
877 shouldRecognizeSimultaneouslyWithGestureRecognizer:
878 (UIGestureRecognizer*)otherGestureRecognizer {
FlutterPlatformViewGestureRecognizersBlockingPolicy
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
@ FlutterPlatformViewGestureRecognizersBlockingPolicyDoNotBlockGesture
BOOL flt_hasFirstResponderInViewHierarchySubtree
NSMutableArray * backdropFilterSubviews()
void CubicTo(const flutter::DlPoint &cp1, const flutter::DlPoint &cp2, const flutter::DlPoint &p2) override
void MoveTo(const flutter::DlPoint &p2, bool will_be_closed) override
void LineTo(const flutter::DlPoint &p2) override
void QuadTo(const flutter::DlPoint &cp, const flutter::DlPoint &p2) override
CGMutablePathRef TakePath() const
BOOL touchedEndedWithoutBlocking
Collection of functions to receive path segments from the underlying path representation via the DlPa...
#define FML_DLOG(severity)
#define FML_DCHECK(condition)
instancetype initWithFrame
impeller::RoundRect DlRoundRect
impeller::Matrix DlMatrix
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
A 4x4 matrix using column-major storage.
constexpr auto GetBottom() const
constexpr Type GetY() const
Returns the Y coordinate of the upper left corner, equivalent to |GetOrigin().y|.
constexpr auto GetTop() const
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
constexpr auto GetLeft() const
constexpr Type GetX() const
Returns the X coordinate of the upper left corner, equivalent to |GetOrigin().x|.
constexpr auto GetRight() const
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.