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 visualEffectView:(UIVisualEffectView*)visualEffectView {
99 if (
self = [super init]) {
105 FML_DLOG(ERROR) <<
"Apple's API for UIVisualEffectView changed. Update the implementation to "
106 "access the gaussianBlur CAFilter.";
109 _backdropFilterView = visualEffectView;
110 _backdropFilterViewConfigured = NO;
122+ (void)prepareOnce:(UIVisualEffectView*)visualEffectView {
126 for (NSUInteger
i = 0;
i < visualEffectView.subviews.count;
i++) {
127 UIView*
view = visualEffectView.subviews[i];
128 if ([NSStringFromClass([
view class]) hasSuffix:
@"BackdropView"]) {
130 for (NSObject* filter in
view.layer.filters) {
131 if ([[filter valueForKey:
@"name"] isEqual:
@"gaussianBlur"] &&
132 [[filter valueForKey:
@"inputRadius"] isKindOfClass:[NSNumber class]]) {
137 }
else if ([NSStringFromClass([
view class]) hasSuffix:
@"VisualEffectSubview"]) {
144+ (
BOOL)isUIVisualEffectViewImplementationValid {
150 if (!
self.backdropFilterViewConfigured) {
151 [
self updateVisualEffectView:_backdropFilterView];
152 self.backdropFilterViewConfigured = YES;
154 return _backdropFilterView;
157- (void)updateVisualEffectView:(UIVisualEffectView*)visualEffectView {
158 NSObject* gaussianBlurFilter = [_gaussianBlurFilter copy];
160 UIView* backdropView = visualEffectView.subviews[_indexOfBackdropView];
161 [gaussianBlurFilter setValue:@(_blurRadius) forKey:@"inputRadius"];
162 backdropView.layer.filters = @[ gaussianBlurFilter ];
164 UIView* visualEffectSubview = visualEffectView.subviews[_indexOfVisualEffectSubview];
165 visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor;
166 visualEffectView.frame = _frame;
168 visualEffectView.layer.cornerRadius = _cornerRadius;
169 visualEffectView.clipsToBounds = YES;
171 self.backdropFilterView = visualEffectView;
178@property(nonatomic, copy) NSArray<PlatformViewFilter*>* filters;
188- (
BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
189 for (UIView*
view in
self.subviews) {
190 if ([
view pointInside:[
self convertPoint:point toView:view] withEvent:event]) {
199 if (
self.filters.count == 0 && filters.count == 0) {
202 self.filters = filters;
203 NSUInteger index = 0;
204 for (index = 0; index <
self.filters.count; index++) {
205 UIVisualEffectView* backdropFilterView;
207 if (
self.backdropFilterSubviews.count <= index) {
208 backdropFilterView = filter.backdropFilterView;
209 [self addSubview:backdropFilterView];
210 [self.backdropFilterSubviews addObject:backdropFilterView];
212 [filter updateVisualEffectView:self.backdropFilterSubviews[index]];
215 for (NSUInteger
i =
self.backdropFilterSubviews.count;
i > index;
i--) {
216 [
self.backdropFilterSubviews[i - 1] removeFromSuperview];
217 [
self.backdropFilterSubviews removeLastObject];
222 if (!_backdropFilterSubviews) {
223 _backdropFilterSubviews = [[NSMutableArray alloc] init];
225 return _backdropFilterSubviews;
240@property(nonatomic) CATransform3D reverseScreenScale;
242- (
fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
247 std::vector<fml::CFRef<CGPathRef>> paths_;
253 return [
self initWithFrame:frame screenScale:[UIScreen mainScreen].scale];
256- (instancetype)
initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale {
258 self.backgroundColor = UIColor.clearColor;
259 _reverseScreenScale = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
267 return [CAShapeLayer class];
270- (CAShapeLayer*)shapeLayer {
271 return (CAShapeLayer*)
self.layer;
278 [
self shapeLayer].path = nil;
279 [
self setNeedsDisplay];
287- (
BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
291- (void)drawRect:(CGRect)rect {
295 CGContextRef context = UIGraphicsGetCurrentContext();
296 CGContextSaveGState(context);
299 CGContextSetAlpha(context, 1);
301 for (
size_t i = 0;
i < paths_.size();
i++) {
302 CGContextAddPath(context, paths_.at(
i));
303 CGContextClip(context);
305 CGContextFillRect(context, rect);
306 CGContextRestoreGState(context);
310 [
super drawRect:rect];
311 if (![
self shapeLayer].path) {
312 if (paths_.size() == 1) {
314 [
self shapeLayer].path = paths_.at(0);
317 CGPathRef pathSoFar = CGPathCreateWithRect(
rectSoFar_, nil);
318 [
self shapeLayer].path = pathSoFar;
319 CGPathRelease(pathSoFar);
327 CGPathRef
path = CGPathCreateWithRect(clipRect, nil);
329 CATransform3D matrixInPoints =
331 paths_.push_back([
self getTransformedPath:
path matrix:matrixInPoints]);
332 CGAffineTransform affine = [
self affineWithMatrix:matrixInPoints];
334 if (affine.b == 0 && affine.c == 0) {
342 if (clipDlRRect.IsEmpty()) {
344 }
else if (clipDlRRect.IsRect()) {
345 [
self clipRect:clipDlRRect.GetBounds() matrix:matrix];
348 CGPathRef pathRef =
nullptr;
351 if (clipDlRRect.GetRadii().AreAllCornersSame()) {
353 auto radii = clipDlRRect.GetRadii();
355 CGPathCreateWithRoundedRect(clipRect, radii.top_left.width, radii.top_left.height, nil);
357 CGMutablePathRef mutablePathRef = CGPathCreateMutable();
361 auto top = clipDlRect.
GetTop();
375 CGPathMoveToPoint(mutablePathRef, nil,
376 left + top_left.width, top);
378 CGPathAddLineToPoint(mutablePathRef, nil,
379 right - top_right.width, top);
380 CGPathAddCurveToPoint(mutablePathRef, nil,
382 right, top + top_right.height,
383 right, top + top_right.height);
385 CGPathAddLineToPoint(mutablePathRef, nil,
386 right, bottom - bottom_right.height);
387 CGPathAddCurveToPoint(mutablePathRef, nil,
389 right - bottom_right.width, bottom,
390 right - bottom_right.width, bottom);
392 CGPathAddLineToPoint(mutablePathRef, nil,
393 left + bottom_left.width, bottom);
394 CGPathAddCurveToPoint(mutablePathRef, nil,
396 left, bottom - bottom_left.height,
397 left, bottom - bottom_left.height);
399 CGPathAddLineToPoint(mutablePathRef, nil,
400 left, top + top_left.height);
401 CGPathAddCurveToPoint(mutablePathRef, nil,
403 left + top_left.width, top,
404 left + top_left.width, top);
405 CGPathCloseSubpath(mutablePathRef);
406 pathRef = mutablePathRef;
409 CATransform3D matrixInPoints =
414 paths_.push_back([
self getTransformedPath:pathRef matrix:matrixInPoints]);
421 CGPathReceiver receiver;
426 dlPath.Dispatch(receiver);
429 CATransform3D matrixInPoints =
431 paths_.push_back([
self getTransformedPath:receiver.TakePath() matrix:matrixInPoints]);
434- (CGAffineTransform)affineWithMatrix:(CATransform3D)matrix {
435 return CGAffineTransformMake(matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.m41,
439- (
fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {
440 CGAffineTransform affine = [
self affineWithMatrix:matrix];
441 CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &affine);
453@property(nonatomic) NSUInteger capacity;
457@property(nonatomic) NSMutableSet<FlutterClippingMaskView*>* pool;
463- (instancetype)initWithCapacity:(NSInteger)capacity {
464 if (
self = [super init]) {
467 _pool = [[NSMutableSet alloc] initWithCapacity:1];
468 _capacity = capacity;
475 if (
self.pool.count == 0) {
478 screenScale:UIScreen.mainScreen.scale];
481 maskView.frame = frame;
483 [
self.pool removeObject:maskView];
490 if (
self.pool.count ==
self.capacity) {
493 [
self.pool addObject:maskView];
498@implementation UIView (FirstResponder)
500 if (
self.isFirstResponder) {
503 for (UIView* subview in
self.subviews) {
504 if (subview.flt_hasFirstResponderInViewHierarchySubtree) {
513@property(nonatomic, weak, readonly) UIView* embeddedView;
519- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
521 gestureRecognizersBlockingPolicy:
523 self = [
super initWithFrame:embeddedView.frame];
525 self.multipleTouchEnabled = YES;
526 _embeddedView = embeddedView;
527 embeddedView.autoresizingMask =
528 (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
530 [
self addSubview:embeddedView];
534 platformViewsController:platformViewsController];
536 _delayingRecognizer =
539 forwardingRecognizer:forwardingRecognizer];
540 _blockingPolicy = blockingPolicy;
542 [
self addGestureRecognizer:_delayingRecognizer];
543 [
self addGestureRecognizer:forwardingRecognizer];
548- (void)forceResetForwardingGestureRecognizerState {
556 [oldForwardingRecognizer recreateRecognizerWithTarget:self];
557 self.delayingRecognizer.forwardingRecognizer = newForwardingRecognizer;
558 [
self removeGestureRecognizer:oldForwardingRecognizer];
559 [
self addGestureRecognizer:newForwardingRecognizer];
562- (void)releaseGesture {
563 self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
566- (
BOOL)containsWebView:(UIView*)view remainingSubviewDepth:(
int)remainingSubviewDepth {
567 if (remainingSubviewDepth < 0) {
570 if ([
view isKindOfClass:[WKWebView class]]) {
573 for (UIView* subview in
view.subviews) {
574 if ([
self containsWebView:subview remainingSubviewDepth:remainingSubviewDepth - 1]) {
581- (void)searchAndFixWebView:(UIView*)view {
582 if ([
view isKindOfClass:[WKWebView class]]) {
583 return [
self searchAndFixWebViewGestureRecognzier:view];
585 for (UIView* subview in
view.subviews) {
586 [
self searchAndFixWebView:subview];
591- (void)searchAndFixWebViewGestureRecognzier:(UIView*)view {
592 for (UIGestureRecognizer* recognizer in
view.gestureRecognizers) {
603 if (recognizer.enabled &&
604 [NSStringFromClass([recognizer
class]) hasSuffix:
@"TouchEventsGestureRecognizer"]) {
605 recognizer.enabled = NO;
606 recognizer.enabled = YES;
609 for (UIView* subview in
view.subviews) {
610 [
self searchAndFixWebViewGestureRecognzier:subview];
614- (void)blockGesture {
615 switch (_blockingPolicy) {
618 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
628 if (@available(iOS 26.0, *)) {
633 NSNumber* isWorkaroundDisabled =
634 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTDisableWebViewGestureReset"];
635 if (!isWorkaroundDisabled.boolValue) {
636 [
self searchAndFixWebView:self.embeddedView];
638 }
else if (@available(iOS 18.2, *)) {
645 if ([
self containsWebView:
self.embeddedView remainingSubviewDepth:1]) {
646 [
self removeGestureRecognizer:self.delayingRecognizer];
647 [
self addGestureRecognizer:self.delayingRecognizer];
653 if (
self.delayingRecognizer.touchedEndedWithoutBlocking) {
657 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
662 self.delayingRecognizer.shouldEndInNextTouchesEnded = YES;
673- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
676- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
679- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
682- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
685- (
id)accessibilityContainer {
686 return self.flutterAccessibilityContainer;
693- (instancetype)initWithTarget:(
id)target
695 forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
696 self = [
super initWithTarget:target action:action];
698 self.delaysTouchesBegan = YES;
699 self.delaysTouchesEnded = YES;
701 _shouldEndInNextTouchesEnded = NO;
702 _touchedEndedWithoutBlocking = NO;
703 _forwardingRecognizer = forwardingRecognizer;
708- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
709 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
712 return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer !=
self;
715- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
716 shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
717 return otherGestureRecognizer ==
self;
720- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
722 [
super touchesBegan:touches withEvent:event];
725- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
726 if (
self.shouldEndInNextTouchesEnded) {
727 self.state = UIGestureRecognizerStateEnded;
728 self.shouldEndInNextTouchesEnded = NO;
730 self.touchedEndedWithoutBlocking = YES;
732 [
super touchesEnded:touches withEvent:event];
735- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
736 self.state = UIGestureRecognizerStateFailed;
758- (instancetype)initWithTarget:(
id)target
760 self = [
super initWithTarget:target action:nil];
764 _platformViewsController = platformViewsController;
772 platformViewsController:_platformViewsController];
775- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
783 [_flutterViewController touchesBegan:touches withEvent:event];
787- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
788 [_flutterViewController touchesMoved:touches withEvent:event];
791- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
792 [_flutterViewController touchesEnded:touches withEvent:event];
799 self.state = UIGestureRecognizerStateFailed;
801 [
self forceResetStateIfNeeded];
805- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
811 [_flutterViewController forceTouchesCancelled:touches];
814 self.state = UIGestureRecognizerStateFailed;
816 [
self forceResetStateIfNeeded];
820- (void)forceResetStateIfNeeded {
822 dispatch_async(dispatch_get_main_queue(), ^{
827 if (strongSelf.state != UIGestureRecognizerStatePossible) {
828 [(FlutterTouchInterceptingView*)strongSelf.view forceResetForwardingGestureRecognizerState];
833- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
834 shouldRecognizeSimultaneouslyWithGestureRecognizer:
835 (UIGestureRecognizer*)otherGestureRecognizer {
FlutterPlatformViewGestureRecognizersBlockingPolicy
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
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|.